IdentityCommand.SCA.psm1
|
function ConvertFrom-SCAApiValue { <# .SYNOPSIS Translates a numeric value returned by the SCA API to its name and meaning. .DESCRIPTION The reverse of ConvertTo-SCAApiValue. Given a numeric value from an API response, returns the friendly name and the description of what the value means, as defined by Get-SCAApiValueMap. A value the module has no definition for is not an error - the returned object reports a null name and description, so an enumeration extended by the API does not break the calling command. .PARAMETER Name The enumeration to look the value up in. .PARAMETER Value The numeric value returned by the API. .EXAMPLE ConvertFrom-SCAApiValue -Name PolicyStatus -Value 1 Returns an object reporting Value 1, Name 'Active' and Description 'The policy is active'. #> [OutputType([pscustomobject])] [CmdletBinding()] param( [parameter( Mandatory = $true, Position = 0 )] [ValidateSet('CloudProvider', 'PolicyStatus')] [string]$Name, [parameter( Mandatory = $true, Position = 1, ValueFromPipeline = $true )] [int]$Value ) begin { $Map = Get-SCAApiValueMap -Name $Name }#begin process { $MemberName = $Map.Keys | Where-Object { $Map[$PSItem]['Value'] -eq $Value } | Select-Object -First 1 if ($null -eq $MemberName) { Write-Verbose "$Value is not a $Name value defined by this module" } [pscustomobject]@{ Value = $Value Name = $MemberName Description = if ($null -ne $MemberName) { $Map[$MemberName]['Description'] } else { $null } } }#process } function ConvertTo-SCAApiValue { <# .SYNOPSIS Translates a friendly value to the numeric value the SCA API expects. .DESCRIPTION A handful of SCA fields are numeric enumerations - the cloud provider a policy or scan applies to, and the status a policy query filters on. Commands take the readable name from the caller and convert it here, against the enumeration defined by Get-SCAApiValueMap. .PARAMETER Name The enumeration to look the value up in. CloudProvider - AWS = 0, GCP = 1, AZURE = 2, AWS_IDC = 3, AZURE_ENTRA_ID = 4 PolicyStatus - Active = 1, Expired = 3, Error = 4, Warning = 5, Validating = 6 .PARAMETER Value The friendly value to translate. Matched case-insensitively. .EXAMPLE ConvertTo-SCAApiValue -Name CloudProvider -Value AWS Returns 0 .EXAMPLE ConvertTo-SCAApiValue -Name PolicyStatus -Value Validating Returns 6 #> [OutputType([int])] [CmdletBinding()] param( [parameter( Mandatory = $true, Position = 0 )] [ValidateSet('CloudProvider', 'PolicyStatus')] [string]$Name, [parameter( Mandatory = $true, Position = 1, ValueFromPipeline = $true )] [string]$Value ) begin { $Map = Get-SCAApiValueMap -Name $Name }#begin process { $Member = $Map[$Value] if ($null -eq $Member) { throw "'$Value' is not a supported $Name value. Expected one of: $(($Map.Keys | Sort-Object) -join ', ')" } $Member['Value'] }#process } function Get-SCAApiValueMap { <# .SYNOPSIS Returns the definition of an SCA API numeric enumeration. .DESCRIPTION A handful of SCA fields are numeric enumerations - the cloud provider a policy or scan applies to, and the status of a policy. This helper is the single definition of those enumerations: the friendly name of each member, the numeric value the API uses for it, and a description of what it means. ConvertTo-SCAApiValue translates a friendly name to the numeric value for a request; and ConvertFrom-SCAApiValue translates a numeric value from a response back to its name and description. .PARAMETER Name The enumeration to return. .EXAMPLE (Get-SCAApiValueMap -Name PolicyStatus)['Active'].Value Returns 1 .EXAMPLE (Get-SCAApiValueMap -Name CloudProvider).Keys Returns the supported cloud provider names. #> [OutputType([System.Collections.Specialized.OrderedDictionary])] [CmdletBinding()] param( [parameter( Mandatory = $true, Position = 0 )] [ValidateSet('CloudProvider', 'PolicyStatus')] [string]$Name ) $Map = @{ 'CloudProvider' = [ordered]@{ 'AWS' = [ordered]@{ Value = 0; Description = 'Amazon Web Services (IAM)' } 'GCP' = [ordered]@{ Value = 1; Description = 'Google Cloud' } 'AZURE' = [ordered]@{ Value = 2; Description = 'Azure (resource)' } 'AWS_IDC' = [ordered]@{ Value = 3; Description = 'Amazon Web Services (IAM Identity Center)' } 'AZURE_ENTRA_ID' = [ordered]@{ Value = 4; Description = 'Azure (Microsoft Entra ID)' } } 'PolicyStatus' = [ordered]@{ 'Active' = [ordered]@{ Value = 1; Description = 'The policy is active' } 'Expired' = [ordered]@{ Value = 3; Description = 'The policy has expired' } 'Error' = [ordered]@{ Value = 4; Description = 'There is an error in the policy' } 'Warning' = [ordered]@{ Value = 5; Description = 'The policy is active, with a warning' } 'Validating' = [ordered]@{ Value = 6; Description = 'The policy is currently being validated' } } } $Map[$Name] } function Get-SCAPolicyApiHeader { <# .SYNOPSIS Returns the header which selects the version of the SCA Policies API to call. .DESCRIPTION The SCA Policies API exposes two request/response shapes, selected by the X-API-Version request header. This module works exclusively with version 2.0 - the version CyberArk recommends - so every policy command sends the same header, formed here. .EXAMPLE Invoke-IDRestMethod -Uri $URI -Method GET -Headers (Get-SCAPolicyApiHeader) #> [OutputType([hashtable])] [CmdletBinding()] param() @{ 'X-API-Version' = '2.0' } } function Get-SCAPolicyTextPattern { <# .SYNOPSIS Returns the pattern the SCA API accepts for a policy name or description. .DESCRIPTION The API constrains the characters allowed in a policy name and description, and publishes a regular expression for each. Both are reproduced here verbatim, as the single definition used by Test-SCAPolicyText. Each pattern contains a character range rather than the list of literal characters its layout suggests, so the accepted set is wider than it appears. Neither accepts tab, newline or any non-ASCII character, and both reject the quote, bracket and ampersand characters commonly used in free text. .PARAMETER Name The policy field to return the pattern for. .EXAMPLE Get-SCAPolicyTextPattern -Name Name Returns the pattern a policy name must match. #> [OutputType([string])] [CmdletBinding()] param( [parameter( Mandatory = $true, Position = 0 )] [ValidateSet('Name', 'Description')] [string]$Name ) $Pattern = @{ #Accepts a space and ! + , - . / 0-9 : ; < = > ? @ A-Z [ \ _ a-z 'Name' = '^[A-Za-z0-9-@ _!.+-\\s]{1,200}$' #As Name, and additionally $ ] ^ { } 'Description' = '^[A-Za-z0-9-@ _!^[{}.+-\]$\\s]{0,200}$' } $Pattern[$Name] } function Resolve-SCAJobStatus { <# .SYNOPSIS Reports the status of the job started by an asynchronous request. .DESCRIPTION Some SCA operations are asynchronous, and their response carries nothing except the identifier of the job which was started. The commands calling those endpoints pass the response here, and the status of that job is returned in its place. The API is inconsistent about which field carries the identifier - 'jobId' from the policy endpoints, 'job_id' from web app creation - so both are accepted. A response carrying neither field is returned unaltered, so a response which reports more than the job it started does not have that detail discarded. .PARAMETER Result The response from the asynchronous request. .EXAMPLE Invoke-IDRestMethod -Uri $URI -Method POST -Body $body | Resolve-SCAJobStatus .EXAMPLE Resolve-SCAJobStatus -Result $result #> [CmdletBinding()] param( [parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [psobject]$Result ) process { $JobId = $null $Fields = $Result.PSObject.Properties.Name foreach ($Field in 'jobId', 'job_id') { if (($Fields -contains $Field) -and (-not [string]::IsNullOrEmpty($Result.$Field))) { $JobId = $Result.$Field break } } if ([string]::IsNullOrEmpty($JobId)) { Write-Verbose 'Response contains no job identifier - returning it unaltered' return $Result } Get-SCAJobStatus -jobId $JobId }#process } function Test-SCAPolicyText { <# .SYNOPSIS Validates a policy name or description against the characters the SCA API accepts. .DESCRIPTION Used as the ValidateScript for the policy -name and -description parameters. The API rejects a value containing an unsupported character, so this catches it before the request is sent, and reports which characters were at fault - a plain ValidatePattern would show the caller nothing but the raw expression. Returns $true when the value is acceptable, and throws otherwise, as ValidateScript requires. .PARAMETER Value The value supplied for the parameter. .PARAMETER ParameterName The name of the parameter being validated, used in the error message. .PARAMETER Pattern The pattern the value must match, from Get-SCAPolicyTextPattern. .EXAMPLE Test-SCAPolicyText -Value 'End of year' -ParameterName name -Pattern (Get-SCAPolicyTextPattern -Name Name) Returns $true. #> [OutputType([bool])] [CmdletBinding()] param( [parameter( Mandatory = $true, Position = 0 )] [AllowEmptyString()] [string]$Value, [parameter( Mandatory = $true, Position = 1 )] [string]$ParameterName, [parameter( Mandatory = $true, Position = 2 )] [string]$Pattern ) if ($Value -cmatch $Pattern) { return $true } #Report the offending characters rather than the expression which rejected them $Unsupported = ([char[]]$Value | Where-Object { "$PSItem" -cnotmatch $Pattern } | Select-Object -Unique) -join '' if ([string]::IsNullOrEmpty($Unsupported)) { throw "The policy $ParameterName is not an accepted length." } throw "The policy $ParameterName contains characters the SCA API does not accept: $Unsupported" } # .ExternalHelp IdentityCommand.SCA-help.xml function Connect-SCATenant { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Subdomain')] param( #subdomain [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Subdomain')] [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'SubdomainCredential')] [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'SubdomainSAML')] [ValidateNotNullOrEmpty()] [Alias('subdomain')] [String]$tenant_subdomain, #tenant_url [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'URL')] [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'URLCredential')] [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'URLSAML')] [ValidateNotNullOrEmpty()] [Alias('SCA_url')] [String]$tenant_url, #Credential used to authenticate to CyberArk Identity. Authentication is performed even if an active IdentityCommand session is found, replacing it [parameter(Mandatory = $true, ParameterSetName = 'SubdomainCredential')] [parameter(Mandatory = $true, ParameterSetName = 'URLCredential')] [ValidateNotNullOrEmpty()] [PSCredential]$Credential, #Authenticate as a service user via New-IDPlatformToken (OAuth client_credentials) instead of the interactive New-IDSession [parameter(ParameterSetName = 'SubdomainCredential')] [parameter(ParameterSetName = 'URLCredential')] [Switch]$PlatformToken, #SAML assertion used to authenticate to CyberArk Identity. Authentication is performed even if an active IdentityCommand session is found, replacing it [parameter(Mandatory = $true, ParameterSetName = 'SubdomainSAML')] [parameter(Mandatory = $true, ParameterSetName = 'URLSAML')] [ValidateNotNullOrEmpty()] [String]$SAMLResponse ) begin { $IDSession = Get-IDSession $HaveSession = $null -ne $IDSession.tenant_url $AuthRequested = $PSBoundParameters.ContainsKey('Credential') -or $PSBoundParameters.ContainsKey('SAMLResponse') if ($HaveSession -and $AuthRequested) { Write-Verbose 'Authentication parameters were supplied; authenticating and replacing the existing IdentityCommand session' } }#begin process { $UsingSubdomain = $PSCmdlet.ParameterSetName -like 'Subdomain*' #Resolve service URLs from platform discovery when a subdomain was supplied, or when #authentication is required and the CyberArk Identity URL must be discovered. $ServiceUrl = $null if ($UsingSubdomain) { $ServiceUrl = Resolve-ServiceUrl -Service sca -Subdomain $tenant_subdomain $tenant_url = $ServiceUrl.ServiceUrl } else { #Ensure URL is in expected format - remove trailing slash if provided in Url $tenant_url = $tenant_url -replace '/$', '' if ($AuthRequested) { $ServiceUrl = Resolve-ServiceUrl -Service sca -Url $tenant_url } } if ($AuthRequested -or (-not $HaveSession)) { if (-not $AuthRequested) { throw 'Authenticate with New-IDSession or New-IDPlatformToken, or supply -Credential, and try again' } $IdentityUrl = $ServiceUrl.IdentityUrl if ($PSCmdlet.ShouldProcess($IdentityUrl, 'Authenticate to CyberArk Identity')) { if ($PSCmdlet.ParameterSetName -like '*SAML') { $null = New-IDSession -tenant_url $IdentityUrl -SAMLResponse $SAMLResponse } elseif ($PlatformToken) { $null = New-IDPlatformToken -tenant_url $IdentityUrl -Credential $Credential } else { $null = New-IDSession -tenant_url $IdentityUrl -Credential $Credential } $IDSession = Get-IDSession } } #Make the CyberArk Identity Session available in the IdentityCommand.SCA scope foreach ($key in $IDSession.keys) { if ($null -ne $IDSession[$key]) { $ISPSSSession[$key] = $IDSession[$key] } } #Set the SCA URL in the session data $ISPSSSession.tenant_url = $tenant_url }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Get-SCAEligibleGroup { [CmdletBinding()] param( [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('AZURE')] [String]$csp = 'AZURE', [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateRange(1, 100)] [int]$limit ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/access/$csp/eligibility/groups" $URI = Add-QueryString -URI $URI -Parameter ($PSBoundParameters | Get-Parameter -ParametersToRemove csp) #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method GET if ($null -ne $result) { Get-PagedResult -InitialResult $result -URI $URI -Style Cursor -ResultProperty response -CursorRequestKey nextToken -CursorResponseKey nextToken } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Get-SCAEligibleTarget { [CmdletBinding()] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('AWS', 'AZURE', 'GCP')] [String]$csp, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateRange(1, 100)] [int]$limit ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/access/$csp/eligibility" $URI = Add-QueryString -URI $URI -Parameter ($PSBoundParameters | Get-Parameter -ParametersToRemove csp) #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method GET if ($null -ne $result) { Get-PagedResult -InitialResult $result -URI $URI -Style Cursor -ResultProperty response -CursorRequestKey nextToken -CursorResponseKey nextToken } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Get-SCAJobStatus { [CmdletBinding()] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [Alias('job_id')] [String]$jobId ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/integrations/status" $URI = Add-QueryString -URI $URI -Parameter ($PSBoundParameters | Get-Parameter) -SupportsDebug #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method GET if ($null -ne $result) { $result } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Get-SCAModuleData { [CmdletBinding()] param() begin { }#begin process { #Calculate the time elapsed since the start of the session and include in return data if ($null -ne $ISPSSSession.StartTime) { $ISPSSSession.ElapsedTime = '{0:HH:mm:ss}' -f ([datetime]$($(Get-Date) - $($ISPSSSession.StartTime)).Ticks) } else { $ISPSSSession.ElapsedTime = $null } #Deep Copy the $ISPSSSession session object and return as IdCmd Session type. Get-SessionClone -InputObject $ISPSSSession | Add-CustomType -Type IdCmd.Session }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Get-SCAOnDemandConfig { [CmdletBinding()] param() begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/on-demand/config" #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method GET if ($null -ne $result) { $result } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Get-SCAPolicy { [CmdletBinding(DefaultParameterSetName = 'List')] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ById' )] [Alias('policyId')] [String]$policy_id, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'List' )] [ValidateSet('Active', 'Expired', 'Error', 'Validating')] [String]$status, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'List' )] [String]$free_text, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'List' )] [ValidateSet('AWS', 'GCP', 'AZURE', 'AWS_IDC', 'AZURE_ENTRA_ID')] [String]$cloud_provider ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/policies" if ($PSCmdlet.ParameterSetName -eq 'ById') { $URI = Add-QueryString -URI "$URI/$policy_id" -SupportsDebug } else { $boundParameters = $PSBoundParameters | Get-Parameter #The API filters on the numeric value of these fields if ($PSBoundParameters.ContainsKey('status')) { $boundParameters['status'] = ConvertTo-SCAApiValue -Name PolicyStatus -Value $status } if ($PSBoundParameters.ContainsKey('cloud_provider')) { $boundParameters['cloud_provider'] = ConvertTo-SCAApiValue -Name CloudProvider -Value $cloud_provider } $URI = Add-QueryString -URI $URI -Parameter $boundParameters -SupportsDebug } #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method GET -Headers $(Get-SCAPolicyApiHeader) if ($null -ne $result) { if ($PSCmdlet.ParameterSetName -eq 'ById') { $result } else { $result.hits } } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Get-SCAPolicyStatus { [CmdletBinding()] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [Alias('policyId')] [String]$policy_id ) begin { }#begin process { #The API defines policy_id as both a path and a query parameter for this operation $URI = Add-QueryString -URI "$($ISPSSSession.tenant_url)/api/policies/$policy_id/status" -Parameter @{ 'policy_id' = $policy_id } #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method GET if ($null -ne $result) { #The API returns the status as a bare integer - report what it means alongside it $Status = ConvertFrom-SCAApiValue -Name PolicyStatus -Value $result [pscustomobject]@{ policyId = $policy_id status = $Status.Value statusName = $Status.Name description = $Status.Description } | Add-CustomType -Type IdCmd.SCA.Policy.Status } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Get-SCASession { [CmdletBinding(DefaultParameterSetName = 'All')] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ByUser' )] [String]$userId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('AWS', 'AZURE', 'GCP')] [String]$csp, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateRange(1, 50)] [int]$limit ) begin { }#begin process { $URI = if ($PSCmdlet.ParameterSetName -eq 'ByUser') { "$($ISPSSSession.tenant_url)/api/access/users/$userId/sessions" } else { "$($ISPSSSession.tenant_url)/api/access/sessions" } $URI = Add-QueryString -URI $URI -Parameter ($PSBoundParameters | Get-Parameter -ParametersToRemove userId) #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method GET if ($null -ne $result) { Get-PagedResult -InitialResult $result -URI $URI -Style Cursor -ResultProperty response -CursorRequestKey nextToken -CursorResponseKey nextToken } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function New-SCAAccessTargetDefinition { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function does not change state')] [OutputType('IdCmd.SCA.Definition.Access.Target')] [CmdletBinding(DefaultParameterSetName = 'ByRoleId')] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ByRoleId' )] [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ByRoleName' )] [String]$workspaceId, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ByRoleId' )] [String]$roleId, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ByRoleName' )] [String]$roleName, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $false, HelpMessage = 'Accepts previous output from New-SCAAccessTargetDefinition, to which this target is appended.' )] [PSTypeName('IdCmd.SCA.Definition.Access.Target')] [psobject[]]$Definition ) begin { $ExpectedProperties = @('workspaceId', 'roleId', 'roleName') }#begin process { $boundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove Definition $Target = Select-RequestProperty -Property $ExpectedProperties -BoundParameter $boundParameters if ($PSBoundParameters.ContainsKey('Definition')) { $Definition } $Target | Add-CustomType -Type IdCmd.SCA.Definition.Access.Target }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function New-SCAPolicy { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('AWS', 'GCP', 'AZURE')] [String]$csp, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 200)] [ValidateScript({ Test-SCAPolicyText -Value $PSItem -ParameterName name -Pattern $(Get-SCAPolicyTextPattern -Name Name) })] [String]$name, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(0, 200)] [ValidateScript({ Test-SCAPolicyText -Value $PSItem -ParameterName description -Pattern $(Get-SCAPolicyTextPattern -Name Description) })] [String]$description, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [datetime]$startDate, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [datetime]$endDate, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true, HelpMessage = 'Accepts object output from New-SCAPolicyRoleDefinition.' )] [PSTypeName('IdCmd.SCA.Definition.Policy.Role')] [psobject[]]$roles, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true, HelpMessage = 'Accepts object output from New-SCAPolicyIdentityDefinition.' )] [PSTypeName('IdCmd.SCA.Definition.Policy.Identity')] [psobject[]]$identities, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, HelpMessage = 'Accepts object output from New-SCAPolicyAccessRuleDefinition.' )] [PSTypeName('IdCmd.SCA.Definition.Policy.AccessRule')] [psobject]$accessRules ) begin { $ExpectedProperties = @('csp', 'name', 'description', 'startDate', 'endDate', 'roles', 'identities', 'accessRules') }#begin process { $URI = Add-QueryString -URI "$($ISPSSSession.tenant_url)/api/policies/create-policy" -SupportsDebug #Get request parameters $boundParameters = $PSBoundParameters | Get-Parameter #The API expects ISO format dates foreach ($dateParam in 'startDate', 'endDate') { if ($PSBoundParameters.ContainsKey($dateParam)) { $boundParameters[$dateParam] = ConvertTo-DateString -Date $PSBoundParameters[$dateParam] } } #Project supplied parameters onto the expected request properties $Properties = Select-RequestProperty -Property $ExpectedProperties -BoundParameter $boundParameters #Create Request Body $body = ConvertTo-JsonBody -Body $Properties if ($PSCmdlet.ShouldProcess($name, 'Create New SCA Policy')) { #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body $body -Headers $(Get-SCAPolicyApiHeader) if ($null -ne $result) { #Report the status of the job started by the request $result | Resolve-SCAJobStatus } } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function New-SCAPolicyAccessRuleDefinition { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function does not change state')] [OutputType('IdCmd.SCA.Definition.Policy.AccessRule')] [CmdletBinding()] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday')] [String[]]$days, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidatePattern('^([01]\d|2[0-3]):([0-5]\d)(:([0-5]\d))?$')] [String]$fromTime, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidatePattern('^([01]\d|2[0-3]):([0-5]\d)(:([0-5]\d))?$')] [String]$toTime, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateRange(1, 24)] [int]$maxSessionDuration, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [String]$timeZone ) begin { $ExpectedProperties = @('days', 'fromTime', 'toTime', 'maxSessionDuration', 'timeZone') }#begin process { $boundParameters = $PSBoundParameters | Get-Parameter $AccessRule = Select-RequestProperty -Property $ExpectedProperties -BoundParameter $boundParameters #The API expects an array of day names, which a single supplied day must not collapse to a string. $AccessRule['days'] = @($days) $AccessRule | Add-CustomType -Type IdCmd.SCA.Definition.Policy.AccessRule }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function New-SCAPolicyIdentityDefinition { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function does not change state')] [OutputType('IdCmd.SCA.Definition.Policy.Identity')] [CmdletBinding()] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [String]$entityName, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [String]$entitySourceId, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('user', 'role', 'group')] [String]$entityClass, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $false, HelpMessage = 'Accepts previous output from New-SCAPolicyIdentityDefinition, to which this identity is appended.' )] [PSTypeName('IdCmd.SCA.Definition.Policy.Identity')] [psobject[]]$Definition ) begin { $ExpectedProperties = @('entityName', 'entitySourceId', 'entityClass') }#begin process { $boundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove Definition $Identity = Select-RequestProperty -Property $ExpectedProperties -BoundParameter $boundParameters if ($PSBoundParameters.ContainsKey('Definition')) { $Definition } $Identity | Add-CustomType -Type IdCmd.SCA.Definition.Policy.Identity }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function New-SCAPolicyRoleDefinition { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function does not change state')] [OutputType('IdCmd.SCA.Definition.Policy.Role')] [CmdletBinding()] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [String]$entityId, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [String]$entitySourceId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [String]$workspaceType, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [String]$organizationId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $false, HelpMessage = 'Accepts previous output from New-SCAPolicyRoleDefinition, to which this role is appended.' )] [PSTypeName('IdCmd.SCA.Definition.Policy.Role')] [psobject[]]$Definition ) begin { $ExpectedProperties = @('entityId', 'workspaceType', 'entitySourceId', 'organizationId') }#begin process { $boundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove Definition $Role = Select-RequestProperty -Property $ExpectedProperties -BoundParameter $boundParameters if ($PSBoundParameters.ContainsKey('Definition')) { $Definition } $Role | Add-CustomType -Type IdCmd.SCA.Definition.Policy.Role }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function New-SCAScanEntityDefinition { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function does not change state')] [OutputType('IdCmd.SCA.Definition.Scan.EntityId')] [CmdletBinding()] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [String]$account_id, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [String]$org_id, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $false, HelpMessage = 'Accepts previous output from New-SCAScanEntityDefinition, to which this entity is appended.' )] [PSTypeName('IdCmd.SCA.Definition.Scan.EntityId')] [psobject[]]$Definition ) begin { $ExpectedProperties = @('org_id', 'account_id') }#begin process { $boundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove Definition $Entity = Select-RequestProperty -Property $ExpectedProperties -BoundParameter $boundParameters if ($PSBoundParameters.ContainsKey('Definition')) { $Definition } $Entity | Add-CustomType -Type IdCmd.SCA.Definition.Scan.EntityId }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function New-SCAWebApp { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('AWS IAM', 'AWS IdC', 'Azure', 'GCP')] [String]$appType, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [String]$appName, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [String]$workspaceId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [bool]$establishAutoTrust, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [bool]$useIdentityAsIdp ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/onboarding/apps" $boundParameters = $PSBoundParameters | Get-Parameter #The workspace details are sent as a nested appMetadata object $Properties = Select-RequestProperty -Property @('appType', 'appName') -BoundParameter $boundParameters $Properties['appMetadata'] = Select-RequestProperty -Property @('workspaceId', 'establishAutoTrust', 'useIdentityAsIdp') -BoundParameter $boundParameters #Create Request Body $body = ConvertTo-JsonBody -Body $Properties if ($PSCmdlet.ShouldProcess($appName, 'Create SCA Web App')) { #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body $body if ($null -ne $result) { #Report the status of the job started by the request $result | Resolve-SCAJobStatus } } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Remove-SCAPolicy { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [Alias('policyId')] [String]$policy_id ) begin { }#begin process { $URI = Add-QueryString -URI "$($ISPSSSession.tenant_url)/api/policies/$policy_id" -SupportsDebug if ($PSCmdlet.ShouldProcess($policy_id, 'Remove SCA Policy')) { #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method DELETE -Headers $(Get-SCAPolicyApiHeader) if ($null -ne $result) { $result } } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Request-SCAAccess { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('AWS', 'AZURE', 'GCP')] [String]$csp, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true, HelpMessage = 'Accepts object output from New-SCAAccessTargetDefinition.' )] [PSTypeName('IdCmd.SCA.Definition.Access.Target')] [ValidateCount(1, 5)] [psobject[]]$targets, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [String]$organizationId ) begin { $ExpectedProperties = @('organizationId', 'csp', 'targets') }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/access/elevate" #Project supplied parameters onto the expected request properties $Properties = Select-RequestProperty -Property $ExpectedProperties -BoundParameter ($PSBoundParameters | Get-Parameter) #Create Request Body $body = ConvertTo-JsonBody -Body $Properties if ($PSCmdlet.ShouldProcess("$csp targets", 'Elevate Access')) { #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body $body if ($null -ne $result) { $result.response } } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Request-SCAGroupMembership { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [String]$directoryId, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateCount(1, 5)] [String[]]$groupId, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('AZURE')] [String]$csp = 'AZURE' ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/access/elevate/groups" $Properties = [ordered]@{ 'directoryId' = $directoryId 'csp' = $csp 'targets' = @($groupId | ForEach-Object { @{ 'groupId' = $PSItem } }) } #Create Request Body $body = ConvertTo-JsonBody -Body $Properties if ($PSCmdlet.ShouldProcess($($groupId -join ', '), 'Request Just-In-Time Group Membership')) { #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body $body if ($null -ne $result) { $result } } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Revoke-SCASession { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'BySessionId')] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'BySessionId' )] [Alias('sessionId')] [ValidateCount(1, 100)] [String[]]$sessionIds, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ByUser' )] [String]$userId ) begin { }#begin process { if ($PSCmdlet.ParameterSetName -eq 'ByUser') { $URI = "$($ISPSSSession.tenant_url)/api/access/users/$userId/sessions/revoke" $Target = $userId $body = $null } else { $URI = "$($ISPSSSession.tenant_url)/api/access/sessions/revoke" $Target = $sessionIds -join ', ' #Create Request Body $body = ConvertTo-JsonBody -Body @{ 'sessionIds' = @($sessionIds) } } if ($PSCmdlet.ShouldProcess($Target, 'Revoke SCA Session')) { #Send Request $result = if ($null -eq $body) { Invoke-IDRestMethod -Uri $URI -Method POST } else { Invoke-IDRestMethod -Uri $URI -Method POST -Body $body } if ($null -ne $result) { $result.response } } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Set-SCAOnDemandConfig { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('In-platform', 'External')] [String]$ApprovalChannel ) begin { }#begin process { $URI = "$($ISPSSSession.tenant_url)/api/on-demand/config" #Create Request Body $body = ConvertTo-JsonBody -Body ($PSBoundParameters | Get-Parameter) if ($PSCmdlet.ShouldProcess($ApprovalChannel, 'Set SCA On-Demand Approval Channel')) { #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method PATCH -Body $body if ($null -ne $result) { $result } } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Set-SCAPolicy { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [Alias('policyId')] [String]$policy_id, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(1, 200)] [ValidateScript({ Test-SCAPolicyText -Value $PSItem -ParameterName name -Pattern $(Get-SCAPolicyTextPattern -Name Name) })] [String]$name, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [ValidateLength(0, 200)] [ValidateScript({ Test-SCAPolicyText -Value $PSItem -ParameterName description -Pattern $(Get-SCAPolicyTextPattern -Name Description) })] [String]$description, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [datetime]$startDate, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [datetime]$endDate, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, HelpMessage = 'Accepts object output from New-SCAPolicyRoleDefinition.' )] [PSTypeName('IdCmd.SCA.Definition.Policy.Role')] [psobject[]]$roles, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, HelpMessage = 'Accepts object output from New-SCAPolicyIdentityDefinition.' )] [PSTypeName('IdCmd.SCA.Definition.Policy.Identity')] [psobject[]]$identities, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, HelpMessage = 'Accepts object output from New-SCAPolicyAccessRuleDefinition.' )] [PSTypeName('IdCmd.SCA.Definition.Policy.AccessRule')] [psobject]$accessRules ) begin { $OrderedProperties = [ordered]@{ 'name' = $null 'description' = $null 'startDate' = $null 'endDate' = $null 'roles' = @() 'identities' = @() 'accessRules' = $null } }#begin process { $URI = Add-QueryString -URI "$($ISPSSSession.tenant_url)/api/policies/$policy_id" -SupportsDebug #Get existing policy settings $PolicySettings = Get-SCAPolicy -policy_id $policy_id #Get request parameters $boundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove policy_id #The API expects ISO format dates foreach ($dateParam in 'startDate', 'endDate') { if ($PSBoundParameters.ContainsKey($dateParam)) { $boundParameters[$dateParam] = ConvertTo-DateString -Date $PSBoundParameters[$dateParam] } } #Project supplied parameters onto the request template, falling back to the existing policy $Properties = Merge-Parameter -Template $OrderedProperties -BoundParameter $boundParameters -Fallback $PolicySettings #Create Request Body $body = ConvertTo-JsonBody -Body $Properties if ($PSCmdlet.ShouldProcess($policy_id, 'Update SCA Policy')) { #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method PUT -Body $body -Headers $(Get-SCAPolicyApiHeader) if ($null -ne $result) { #Report the status of the job started by the request $result | Resolve-SCAJobStatus } } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Start-SCADiscovery { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('AWS', 'GCP', 'AZURE')] [String]$csp, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [String]$organization_id, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [String]$id, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true )] [bool]$new_account ) begin { }#begin process { $URI = Add-QueryString -URI "$($ISPSSSession.tenant_url)/api/policies/discovery" -SupportsDebug $boundParameters = $PSBoundParameters | Get-Parameter #The workspace to discover is sent as a nested account_info object $Properties = Select-RequestProperty -Property @('csp', 'organization_id') -BoundParameter $boundParameters $Properties['account_info'] = Select-RequestProperty -Property @('id', 'new_account') -BoundParameter $boundParameters #Create Request Body $body = ConvertTo-JsonBody -Body $Properties if ($PSCmdlet.ShouldProcess($id, 'Start SCA Workspace Discovery')) { #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body $body if ($null -ne $result) { $result } } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Start-SCAScan { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('AWS', 'GCP', 'AZURE')] [String]$cloudProvider, [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [ValidateSet('All', 'Specific')] [String]$accountType, [parameter( Mandatory = $false, ValueFromPipelinebyPropertyName = $true, HelpMessage = 'Accepts object output from New-SCAScanEntityDefinition.' )] [PSTypeName('IdCmd.SCA.Definition.Scan.EntityId')] [psobject[]]$entityIds ) begin { $ExpectedProperties = @('cloudProvider', 'accountType', 'entityIds') }#begin process { $URI = Add-QueryString -URI "$($ISPSSSession.tenant_url)/api/cloud/rescan" -SupportsDebug $boundParameters = $PSBoundParameters | Get-Parameter #The API expects the numeric value of the cloud provider $boundParameters['cloudProvider'] = ConvertTo-SCAApiValue -Name CloudProvider -Value $cloudProvider #Project supplied parameters onto the expected request properties $Properties = Select-RequestProperty -Property $ExpectedProperties -BoundParameter $boundParameters #Create Request Body $body = ConvertTo-JsonBody -Body $Properties if ($PSCmdlet.ShouldProcess("$cloudProvider ($accountType)", 'Start SCA Cloud Scan')) { #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body $body if ($null -ne $result) { $result } } }#process end { }#end } # .ExternalHelp IdentityCommand.SCA-help.xml function Test-SCAPolicy { [CmdletBinding(SupportsShouldProcess)] param( [parameter( Mandatory = $true, ValueFromPipelinebyPropertyName = $true )] [Alias('policy_id')] [String]$policyId ) begin { }#begin process { $URI = Add-QueryString -URI "$($ISPSSSession.tenant_url)/api/policies/validate" -SupportsDebug #Create Request Body $body = ConvertTo-JsonBody -Body @{ 'policyId' = $policyId } if ($PSCmdlet.ShouldProcess($policyId, 'Validate SCA Policy')) { #Send Request $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body $body if ($null -ne $result) { #Report the status of the job started by the request $result | Resolve-SCAJobStatus } } }#process end { }#end } #Copy IdentityCommand's private helpers into this module: this module's functions call them, and #the argument completer registrations below do so at import time. #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)}")) } #region Registration $SCAPolicyIdCompleter = Get-ArgumentCompleter -RetrievalCommand 'Get-SCAPolicy' -ValueProperty 'policyId' -LabelProperty 'name' Register-ArgumentCompleter -ParameterName 'policy_id' -ScriptBlock $SCAPolicyIdCompleter -CommandName @( 'Get-SCAPolicy' 'Set-SCAPolicy' 'Remove-SCAPolicy' 'Get-SCAPolicyStatus' ) #Test-SCAPolicy takes the policy id under the API's body field name. Register-ArgumentCompleter -ParameterName 'policyId' -ScriptBlock $SCAPolicyIdCompleter -CommandName 'Test-SCAPolicy' Register-ArgumentCompleter -ParameterName 'sessionIds' -ScriptBlock ( Get-ArgumentCompleter -RetrievalCommand 'Get-SCASession' -ValueProperty 'sessionId' -LabelProperty 'userId' ) -CommandName 'Revoke-SCASession' #The workspace types the API supports differ by cloud provider, so these are offered as completions #rather than enforced with a ValidateSet. $SCAWorkspaceType = [ordered]@{ 'account' = 'AWS - required only for IAM Identity Center' 'directory' = 'Azure - Microsoft Entra ID directory' 'management_group' = 'Azure - management group' 'subscription' = 'Azure - subscription' 'resource_group' = 'Azure - resource group' 'resource' = 'Azure - resource' 'gcp_organization' = 'Google Cloud - organization' 'folder' = 'Google Cloud - folder' 'project' = 'Google Cloud - project' } Register-ArgumentCompleter -ParameterName 'workspaceType' -CommandName 'New-SCAPolicyRoleDefinition' -ScriptBlock { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) #Standard ArgumentCompleter parameters that are not otherwise referenced. $null = $commandName, $parameterName, $commandAst, $fakeBoundParameters $Word = "$wordToComplete".Trim("'`"") $SCAWorkspaceType.Keys | Where-Object { $PSItem -like "$Word*" } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($PSItem, $PSItem, 'ParameterValue', $SCAWorkspaceType[$PSItem]) } }.GetNewClosure() #endregion # Script scope session object for session data $ISPSSSession = [ordered]@{ tenant_url = $null User = $null TenantId = $null SessionId = $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 |