Private/ObjectHelpers.ps1
|
$script:midPointOidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' # REST collection -> midPoint type name used in references. $script:midPointTypeMap = @{ users = 'c:UserType' roles = 'c:RoleType' orgs = 'c:OrgType' shadows = 'c:ShadowType' resources = 'c:ResourceType' } function Get-midPointProperty { # Safe property read on a PSCustomObject (returns $null when missing). # midPoint sometimes qualifies keys with a namespace prefix, e.g. a reference with a filter comes as # { "t:oid": ..., "t:type": ..., "filter": ... }; 'oid' then finds 't:oid'. [CmdletBinding()] param( [AllowNull()] [object] $InputObject, [Parameter(Mandatory)] [string] $Name ) if ($null -eq $InputObject) { return $null } $p = $InputObject.PSObject.Properties[$Name] if (-not $p -and $InputObject -is [System.Management.Automation.PSCustomObject]) { $suffix = ':' + $Name $p = $InputObject.PSObject.Properties | Where-Object { $_.Name.EndsWith($suffix) -and -not $_.Name.StartsWith('@') } | Select-Object -First 1 } if ($p) { $p.Value } } function Get-midPointContainerId { # Container ids (e.g. of assignments) come as '@id' in midPoint's JSON; 'id' is accepted as well. [CmdletBinding()] param([AllowNull()] [object] $InputObject) $id = Get-midPointProperty $InputObject '@id' if ($null -eq $id) { $id = Get-midPointProperty $InputObject 'id' } $id } function ConvertFrom-midPointPolyString { # midPoint sends PolyStrings either as a plain string or as { orig, norm }. [CmdletBinding()] param([AllowNull()] [object] $Value) if ($null -eq $Value) { return $null } if ($Value -is [string]) { return $Value } $orig = Get-midPointProperty $Value 'orig' if ($null -ne $orig) { return [string]$orig } [string]$Value } function ConvertFrom-midPointQName { # 'org:default', 'c:RoleType' or 'http://...#RoleType' -> 'default' / 'RoleType'. [CmdletBinding()] param([AllowNull()] [object] $Value) if ($null -eq $Value -or "$Value" -eq '') { return $null } ([string]$Value -split '[#:]')[-1] } function ConvertTo-midPointDateTime { # [datetime] -> ISO 8601 with offset (midPoint xsd:dateTime). Unspecified kind is treated as local time. [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory)] [datetime] $Value) ([System.DateTimeOffset]$Value).ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", [cultureinfo]::InvariantCulture) } function ConvertFrom-midPointDateTime { # JSON timestamps may already be [datetime] (ConvertFrom-Json does that) or strings. [CmdletBinding()] param([AllowNull()] [object] $Value) if ($null -eq $Value -or "$Value" -eq '') { return $null } if ($Value -is [datetime]) { return $Value } [System.DateTimeOffset]::Parse([string]$Value, [cultureinfo]::InvariantCulture).LocalDateTime } function ConvertTo-midPointActivationStatus { # 'Enabled' -> 'enabled' (ActivationStatusType values are lower case). [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory)] [string] $Value) $Value.ToLowerInvariant() } function ConvertTo-midPointAxiomString { # Quotes a value for an Axiom query: "..." with \ and " escaped. [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory)] [AllowEmptyString()] [string] $Value) '"' + ($Value -replace '\\', '\\' -replace '"', '\"') + '"' } function Get-midPointObjectFromResponse { # Unwraps the JSON envelope: # single object: { "user": { ... } } # search result: { "@ns": ..., "object": { "@type": "...ObjectListType", "object": [ ... ] } } [CmdletBinding()] param([AllowNull()] [object] $Body) if ($null -eq $Body -or $Body -is [string]) { return } $list = Get-midPointProperty $Body 'object' if ($null -ne $list -and $list -isnot [string] -and $null -ne $list.PSObject.Properties['object']) { foreach ($o in @(Get-midPointProperty $list 'object')) { $o } return } if ($null -ne $list -and "$(Get-midPointProperty $list '@type')" -like '*ObjectListType') { return # empty search result } $root = @($Body.PSObject.Properties | Where-Object { -not $_.Name.StartsWith('@') }) if ($root.Count -eq 1) { $root[0].Value } else { $Body } } function Find-midPointObject { # POST <type>/search with an Axiom filter. Returns raw objects. [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Type, [string] $Filter, [int] $MaxResults, [string[]] $Options ) $query = [ordered]@{} if ($Filter) { $query.filter = [ordered]@{ text = $Filter } } if ($MaxResults -gt 0) { $query.paging = [ordered]@{ maxSize = $MaxResults } } $params = @{ Method = 'POST' Path = "$Type/search" Body = [ordered]@{ query = $query } } if ($Options) { $params.Query = @{ options = $Options } } $response = Invoke-midPointRestMethod @params Get-midPointObjectFromResponse -Body $response.Body } function Get-midPointObjectByOid { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Type, [Parameter(Mandatory)] [string] $Oid, [string[]] $Options ) $params = @{ Method = 'GET' Path = '{0}/{1}' -f $Type, [uri]::EscapeDataString($Oid) } if ($Options) { $params.Query = @{ options = $Options } } $response = Invoke-midPointRestMethod @params Get-midPointObjectFromResponse -Body $response.Body } function Resolve-midPointObjectOid { # The -Identity pattern: accepts an object with an Oid (module output or raw midPoint object), # an OID string, or a name. A name is looked up and must match exactly one object. [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)] [AllowNull()] [object] $InputObject, [Parameter(Mandatory)] [string] $Type ) if ($null -eq $InputObject -or "$InputObject" -eq '') { throw [System.ArgumentException]::new("No $Type identity given.") } if ($InputObject -isnot [string]) { $oid = Get-midPointProperty $InputObject 'Oid' if ($oid) { return [string]$oid } $InputObject = [string]$InputObject } if ($InputObject -match $script:midPointOidPattern) { return $InputObject } # A search over resources or shadows must not contact the connectors: without noFetch midPoint # completes schema and capabilities of every resource it finds (and reports a partial error when one fails). $options = if ($Type -in 'resources', 'shadows') { @('noFetch') } else { @() } $found = @(Find-midPointObject -Type $Type -Filter ('name = {0}' -f (ConvertTo-midPointAxiomString $InputObject)) -Options $options) if ($found.Count -eq 0) { $record = [System.Management.Automation.ErrorRecord]::new( [System.Management.Automation.ItemNotFoundException]::new("No object of type '$Type' with name '$InputObject' found."), 'midPointObjectNotFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $InputObject) throw $record } if ($found.Count -gt 1) { throw [System.InvalidOperationException]::new("More than one object of type '$Type' has the name '$InputObject'. Use the OID.") } [string](Get-midPointProperty $found[0] 'oid') } function New-midPointItemDelta { # One itemDelta of an objectModification. Without -Value a 'replace' clears the item. [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Builds an in-memory object only.')] [CmdletBinding()] [OutputType([System.Collections.Specialized.OrderedDictionary])] param( [Parameter(Mandatory)] [ValidateSet('add', 'replace', 'delete')] [string] $ModificationType, [Parameter(Mandatory)] [string] $Path, [AllowNull()] [object] $Value ) $delta = [ordered]@{ modificationType = $ModificationType path = $Path } if ($PSBoundParameters.ContainsKey('Value') -and $null -ne $Value -and -not ($Value -is [string] -and $Value -eq '')) { $values = @($Value) $delta.value = if ($values.Count -eq 1) { $values[0] } else { $values } } $delta } function Invoke-midPointModify { # PATCH <type>/<oid> with a list of item deltas. No deltas send an empty modification # ({ "objectModification": {} }), e.g. for a recompute with -Options reconcile. [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Type, [Parameter(Mandatory)] [string] $Oid, [AllowEmptyCollection()] [object[]] $ItemDelta = @(), [string[]] $Options ) $modification = [ordered]@{} if (@($ItemDelta).Count -gt 0) { $modification.itemDelta = @($ItemDelta) } $params = @{ Method = 'PATCH' Path = '{0}/{1}' -f $Type, [uri]::EscapeDataString($Oid) Body = [ordered]@{ objectModification = $modification } } if ($Options) { $params.Query = @{ options = $Options } } $null = Invoke-midPointRestMethod @params } function Set-midPointNestedValue { # 'activation/validTo' = x -> $target.activation.validTo = x (creates the dictionaries). [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Builds an in-memory object only.')] [CmdletBinding()] param( [Parameter(Mandatory)] [System.Collections.IDictionary] $Target, [Parameter(Mandatory)] [string] $Path, [AllowNull()] [object] $Value ) $parts = $Path.Trim('/') -split '/' $node = $Target for ($i = 0; $i -lt $parts.Count - 1; $i++) { if (-not $node.Contains($parts[$i]) -or $node[$parts[$i]] -isnot [System.Collections.IDictionary]) { $node[$parts[$i]] = [ordered]@{} } $node = $node[$parts[$i]] } $node[$parts[-1]] = $Value } function New-midPointActivationDelta { # Item deltas for administrativeStatus / validFrom / validTo below $Prefix # ('activation' or 'assignment[3]/activation'). Returns an empty list if nothing is set. [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Builds an in-memory object only.')] [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Prefix, [string] $AdministrativeStatus, [object] $ValidFrom, [object] $ValidTo, [switch] $ClearValidFrom, [switch] $ClearValidTo ) if ($ClearValidFrom -and $null -ne $ValidFrom) { throw [System.ArgumentException]::new('Use either -ValidFrom or -ClearValidFrom, not both.') } if ($ClearValidTo -and $null -ne $ValidTo) { throw [System.ArgumentException]::new('Use either -ValidTo or -ClearValidTo, not both.') } $deltas = [System.Collections.Generic.List[object]]::new() if ($AdministrativeStatus) { if ($AdministrativeStatus -eq 'None') { $deltas.Add((New-midPointItemDelta -ModificationType replace -Path "$Prefix/administrativeStatus")) } else { $deltas.Add((New-midPointItemDelta -ModificationType replace -Path "$Prefix/administrativeStatus" -Value (ConvertTo-midPointActivationStatus $AdministrativeStatus))) } } if ($null -ne $ValidFrom) { $deltas.Add((New-midPointItemDelta -ModificationType replace -Path "$Prefix/validFrom" -Value (ConvertTo-midPointDateTime $ValidFrom))) } if ($ClearValidFrom) { $deltas.Add((New-midPointItemDelta -ModificationType replace -Path "$Prefix/validFrom")) } if ($null -ne $ValidTo) { $deltas.Add((New-midPointItemDelta -ModificationType replace -Path "$Prefix/validTo" -Value (ConvertTo-midPointDateTime $ValidTo))) } if ($ClearValidTo) { $deltas.Add((New-midPointItemDelta -ModificationType replace -Path "$Prefix/validTo")) } , $deltas.ToArray() } function Get-midPointIdentityLabel { # Readable label for ShouldProcess / messages: the Name of a module object, else the string. [CmdletBinding()] [OutputType([string])] param([AllowNull()] [object] $InputObject) if ($null -eq $InputObject) { return '' } if ($InputObject -is [string]) { return $InputObject } $name = Get-midPointProperty $InputObject 'Name' if ($name) { return ConvertFrom-midPointPolyString $name } $oid = Get-midPointProperty $InputObject 'Oid' if ($oid) { return [string]$oid } [string]$InputObject } |