From b00d9597fa10b06bdb72d5c28bccf3c2007d64b6 Mon Sep 17 00:00:00 2001 From: Jiri Formacek Date: Mon, 28 Dec 2020 17:46:26 +0100 Subject: [PATCH] Updated implementation Find-LdapObject: Added support for sorting of attributes on returned objects Transforms: Simplified implementation --- S.DS.P.psd1 | 10 +- S.DS.P.psm1 | 144 ++++++++++++++++++------ TransformTemplate/_Template.ps1 | 11 +- Transforms/Boolean.ps1 | 11 +- Transforms/Certificate.ps1 | 10 +- Transforms/GeneralizedTime.ps1 | 47 +------- Transforms/Integer.ps1 | 9 +- Transforms/Long.ps1 | 8 +- Transforms/ProxyAddress.ps1 | 10 +- Transforms/SamAccountType.ps1 | 11 +- Transforms/WindowsHelloKeyInfo.ps1 | 10 +- Transforms/admpwd.e.pwd.ps1 | 9 +- Transforms/admpwd.e.pwdhistory.ps1 | 9 +- Transforms/fileTime.ps1 | 24 ++-- Transforms/groupType.ps1 | 20 ++-- Transforms/guid.ps1 | 9 +- Transforms/quotaUsage.ps1 | 8 +- Transforms/replicationMetadata.ps1 | 12 +- Transforms/searchFlags.ps1 | 12 +- Transforms/securityDescriptor.ps1 | 8 +- Transforms/securityIdentifier.ps1 | 8 +- Transforms/supportedEncryptionTypes.ps1 | 11 +- Transforms/systemFlags.ps1 | 11 +- Transforms/unicodePwd.ps1 | 7 +- Transforms/userAccountControl.ps1 | 13 +-- Transforms/wellKnownObject.ps1 | 10 +- 26 files changed, 194 insertions(+), 258 deletions(-) diff --git a/S.DS.P.psd1 b/S.DS.P.psd1 index 4f2e244..c99a8c2 100644 --- a/S.DS.P.psd1 +++ b/S.DS.P.psd1 @@ -79,13 +79,16 @@ # No need to specify prop as binary if transform loaded for the prop - binary'ness of prop is defined in transform # Added CertificateValidationFlags param to Get-LdapConnection to allow control trust to server certificate # Added Client certificate auth for LDAP connection +# 28.12.2020, 2.1.1, Jiri Formacek, Changed default of RangeSize to -1, which causes that ranged attribute retrieval is not used by default and all objects are loaded within single request +# Simplified implementation of transforms and added some +# Added support for alphabetically sorting of attributes on returned objects @{ # Script module or binary module file associated with this manifest RootModule = '.\S.DS.P.psm1' # Version number of this module. - ModuleVersion = '2.1.0' + ModuleVersion = '2.1.1' # ID used to uniquely identify this module GUID = '766cbbc0-85b9-4773-b4db-2fa86cd771ff' @@ -147,7 +150,8 @@ 'Add-LdapObject','Remove-LdapObject', 'Rename-LdapObject', 'Register-LdapAttributeTransform','Unregister-LdapAttributeTransform', - 'Get-LdapAttributeTransform' + 'Get-LdapAttributeTransform', + 'New-LdapAttributeTransformDefinition' # Variables to export from this module VariablesToExport = @() @@ -181,7 +185,7 @@ # ReleaseNotes = '' # Prerelease string of this module - #Prerelease = 'beta5' + Prerelease = 'beta1' # Flag to indicate whether the module requires explicit user acceptance for install/update/save RequireLicenseAcceptance = $false diff --git a/S.DS.P.psm1 b/S.DS.P.psm1 index a44a8e1..6be4ef5 100644 --- a/S.DS.P.psm1 +++ b/S.DS.P.psm1 @@ -9,7 +9,7 @@ Function Find-LdapObject { Optionally, attribute values can be transformed to complex types using transform registered for an attribute with 'Load' action. .OUTPUTS - Search results as custom objects with requested properties as strings or byte stream + Search results as PSCustomObjects with requested properties as strings, byte streams or complex types produced by transforms .EXAMPLE Find-LdapObject -LdapConnection [string]::Empty -SearchFilter:"(&(sn=smith)(objectClass=user)(objectCategory=organizationalPerson))" -SearchBase:"cn=Users,dc=myDomain,dc=com" @@ -98,12 +98,22 @@ This command connects to given LDAP server and performs the search anonymously. .EXAMPLE $Ldap = Get-LdapConnection -LdapServer:ldap.mycorp.com $dse = Get-RootDSE -LdapConnection $conn -Find-LdapObject -LdapConnection $ldap -SearchFilter:"(&(objectClass=user)(objectCategory=organizationalPerson))" -SearchBase:"ou=People,ou=mycorp,o=world" -RangeSize -1 -PropertiesToLoad * +Find-LdapObject -LdapConnection $ldap -SearchFilter:"(&(objectClass=user)(objectCategory=organizationalPerson))" -SearchBase:"ou=People,ou=mycorp,o=world" -PropertiesToLoad * Description ----------- This command connects to given LDAP server and performs the direct search, retrieving all properties with value from objects found by search +.EXAMPLE +$Ldap = Get-LdapConnection -LdapServer:ldap.mycorp.com +$dse = Get-RootDSE -LdapConnection $conn +Find-LdapObject -LdapConnection $ldap -SearchFilter:"(&(objectClass=group)(objectCategory=group)(cn=MyVeryLargeGroup))" -SearchBase:"ou=People,ou=mycorp,o=world" -PropertiesToLoad member -RageSize 1000 + +Description +----------- +This command connects to given LDAP server and lists all members of the group, using ranged retrieval ("paging support on LDAP attributes") + + .LINK More about System.DirectoryServices.Protocols: http://msdn.microsoft.com/en-us/library/bb332056.aspx #> @@ -154,16 +164,17 @@ More about System.DirectoryServices.Protocols: http://msdn.microsoft.com/en-us/l # Negative value means that attribute values are loaded directly with list of objects # Zero means that ranged attribute value retrieval is disabled and attribute values are returned in single request. # Positive value means that each attribute value is loaded in dedicated requests in batches of given size. Usable for loading of group members - # Note: Default in query policy in AD is 1500; we use 1000 as default here. - # Note: Default value indicates ranged retrieval, so as we're on safe side and do the best for retrieval of all values in attribute, even if it means performance impact - # Default: 1000 - $RangeSize=1000, + # Note: Default in query policy in AD is 1500; make sure that you do not use here higher value than allowed by LDAP server + # Default: -1 (means that ranged attribute retrieval is not used by default) + # IMPORTANT: default changed in v2.1.1 - previously it was 1000. Changed because it typically caused large perforrmance impact when using -PropsToLoad '*' + $RangeSize=-1, [parameter(Mandatory = $false)] [alias('BinaryProperties')] [String[]] #List of properties that we want to load as byte stream. #Note: Those properties must also be present in PropertiesToLoad parameter. Properties not listed here are loaded as strings + #Note: When using transform for a property, then transform "knows" if it's binary or not, so no need to specify it in BinaryProps #Default: empty list, which means that all properties are loaded as strings $BinaryProps=@(), @@ -187,11 +198,43 @@ More about System.DirectoryServices.Protocols: http://msdn.microsoft.com/en-us/l [Timespan] #Number of seconds before request times out. #Default: 120 seconds - $Timeout = (New-Object System.TimeSpan(0,0,120)) + $Timeout = (New-Object System.TimeSpan(0,0,120)), + + [Switch] + #Whether to alphabetically sort attributes on returned objects + $SortAttributes ) Begin { + Function PostProcess { + param + ( + [Parameter(ValueFromPipeline)] + [System.Collections.Hashtable]$data, + [bool]$Sort + ) + + process + { + #Flatten + $coll=@($data.Keys) + foreach($prop in $coll) {$data[$prop] = [Flattener]::FlattenArray($data[$prop])} + if($Sort) + { + #flatten and sort attributes + $coll=@($coll | Sort-Object) + $sortedData=[ordered]@{} + foreach($prop in $coll) {$sortedData[$prop] = $data[$prop]} + #return result to pipeline + [PSCustomObject]$sortedData + } + else { + [PSCustomObject]$data + } + } + } + #remove unwanted props $PropertiesToLoad=@($propertiesToLoad | where-object {$_ -notin @('distinguishedName','1.1')}) #if asterisk in list of props to load, load all props available on object despite of required list @@ -268,19 +311,19 @@ More about System.DirectoryServices.Protocols: http://msdn.microsoft.com/en-us/l {$_ -lt 0} { #directly via single ldap call #some attribs may not be loaded (e.g. computed) - GetResultsDirectlyInternal -rq $rq -conn $LdapConnection -PropertiesToLoad $PropertiesToLoad -AdditionalProperties $AdditionalProperties -BinaryProperties $BinaryProps -Timeout $Timeout + GetResultsDirectlyInternal -rq $rq -conn $LdapConnection -PropertiesToLoad $PropertiesToLoad -AdditionalProperties $AdditionalProperties -BinaryProperties $BinaryProps -Timeout $Timeout | PostProcess -Sort $SortAttributes break } 0 { #query attributes for each object returned using base search #but not using ranged retrieval, so multivalued attributes with many values may not be returned completely - GetResultsIndirectlyInternal -rq $rq -conn $LdapConnection -PropertiesToLoad $PropertiesToLoad -AdditionalProperties $AdditionalProperties -AdditionalControls $AdditionalControls -BinaryProperties $BinaryProps -Timeout $Timeout + GetResultsIndirectlyInternal -rq $rq -conn $LdapConnection -PropertiesToLoad $PropertiesToLoad -AdditionalProperties $AdditionalProperties -AdditionalControls $AdditionalControls -BinaryProperties $BinaryProps -Timeout $Timeout | PostProcess -Sort $SortAttributes break } {$_ -gt 0} { #query attributes for each object returned using base search and each attribute value with ranged retrieval #so even multivalued attributes with many values are returned completely - GetResultsIndirectlyRangedInternal -rq $rq -conn $LdapConnection -PropertiesToLoad $PropertiesToLoad -AdditionalProperties $AdditionalProperties -AdditionalControls $AdditionalControls -BinaryProperties $BinaryProps -Timeout $Timeout -RangeSize $RangeSize + GetResultsIndirectlyRangedInternal -rq $rq -conn $LdapConnection -PropertiesToLoad $PropertiesToLoad -AdditionalProperties $AdditionalProperties -AdditionalControls $AdditionalControls -BinaryProperties $BinaryProps -Timeout $Timeout -RangeSize $RangeSize | PostProcess -Sort $SortAttributes break } } @@ -304,6 +347,7 @@ Function Get-RootDSE { .DESCRIPTION Retrieves LDAP server metadata from Root DSE object Current implementation is specialized to metadata foung on Windows LDAP server, so on other platforms, some metadata may be empty. + Or other platforms may publish interesting metadata not available on Windwos LDAP - feel free to add here .OUTPUTS Custom object containing information about LDAP server @@ -323,7 +367,7 @@ More about System.DirectoryServices.Protocols: http://msdn.microsoft.com/en-us/l [parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.DirectoryServices.Protocols.LdapConnection] #existing LDAPConnection object retrieved via Get-LdapConnection - #When we perform many searches, it is more effective to use the same conbnection rather than create new connection for each search request. + #When we perform many searches, it is more effective to use the same connection rather than create new connection for each search request. $LdapConnection ) Begin @@ -366,7 +410,7 @@ More about System.DirectoryServices.Protocols: http://msdn.microsoft.com/en-us/l } #if there was error, let the exception go to caller and do not continue - #sometimes server does not return nothing if we ask for property that is not supported by protocol + #sometimes server does not return anything if we ask for property that is not supported by protocol if($rsp.Entries.Count -eq 0) { return; } @@ -657,11 +701,13 @@ More about System.DirectoryServices.Protocols: http://msdn.microsoft.com/en-us/l $LdapConnection.SessionOptions.ProtocolVersion=$ProtocolVersion + #store connection params for each server in grobal variable, so as it is reachable from callback scriptblocks $connectionParams=@{} foreach($server in $LdapServer) {$script:ConnectionParams[$server]=$connectionParams} if($CertificateValidationFlags -ne 'NoFlag') { $connectionParams['ServerCertificateValidationFlags'] = $CertificateValidationFlags + #server certificate validation callback $LdapConnection.SessionOptions.VerifyServerCertificate = { param( [Parameter(Mandatory)][DirectoryServices.Protocols.LdapConnection]$LdapConnection, [Parameter(Mandatory)][Security.Cryptography.X509Certificates.X509Certificate2]$Certificate @@ -687,6 +733,8 @@ More about System.DirectoryServices.Protocols: http://msdn.microsoft.com/en-us/l if($null -ne $ClientCertificate) { $connectionParams['ClientCertificate'] = $ClientCertificate + #client certificate retrieval callback + #we just support explicit certificate now $LdapConnection.SessionOptions.QueryClientCertificate = { param( [Parameter(Mandatory)][DirectoryServices.Protocols.LdapConnection]$LdapConnection, [Parameter(Mandatory)][byte[][]]$TrustedCAs @@ -1255,6 +1303,7 @@ More about attribute transforms and how to create them: https://github.com/jform $AttributeName, [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='TransformObject', Position=0)] [PSCustomObject] + #Transform object produced by Get-LdapAttributeTRansform $Transform ) @@ -1304,7 +1353,6 @@ More about attribute transforms and how to create them: https://github.com/jform } } - Function Unregister-LdapAttributeTransform { <# @@ -1324,6 +1372,8 @@ $Ldap = Get-LdapConnection -LdapServer "mydc.mydomain.com" -EncryptionType Kerbe Get-LdapAttributeTransform -ListAvailable #register necessary transforms Register-LdapAttributeTransform -Name Guid -AttributeName objectGuid +#Now objectGuid property on returned object is Guid rather than raw byte array +Find-LdapObject -LdapConnection $Ldap -SearchBase "cn=User1,cn=Users,dc=mydomain,dc=com" -SearchScope Base -PropertiesToLoad 'cn',objectGuid #we no longer need the transform, let's unregister Unregister-LdapAttributeTransform -AttributeName objectGuid @@ -1332,9 +1382,9 @@ Find-LdapObject -LdapConnection $Ldap -SearchBase "cn=User1,cn=Users,dc=mydomain Description ---------- -This example registers transform that converts raw byte array in ntSecurityDescriptor property into instance of System.DirectoryServices.ActiveDirectorySecurity -After command completes, returned object(s) will have instance of System.DirectoryServices.ActiveDirectorySecurity in ntSecurityDescriptor property -Then transform is unregistered, so subsequent calls do not use it +This example registers transform that converts raw byte array in objectGuid property into instance of System.Guid +After command completes, returned object(s) will have instance of System.Guid in objectGuid property +Then the transform is unregistered, so subsequent calls do not use it .LINK @@ -1343,8 +1393,9 @@ More about attribute transforms and how to create them: https://github.com/jform #> + [CmdletBinding()] param ( - [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position=0)] [string] #Name of the attribute to unregister transform from $AttributeName @@ -1401,7 +1452,42 @@ More about attribute transforms and how to create them: https://github.com/jform } } -#Helpers +function New-LdapAttributeTransformDefinition +{ +<# +.SYNOPSIS + Creates definition of transform. Used by transform implementations + +.OUTPUTS + Transform definition + +.LINK +More about attribute transforms and how to create them: https://github.com/jformacek/S.DS.P + +#> + [CmdletBinding()] + param + ( + [Parameter(Mandatory, Position=0)] + [string[]]$SupportedAttributes, + [switch] + #Whether supported attributes need to be loaded from/saved to LDAP as binary stream + $BinaryInput + ) + + process + { + [PSCustomObject][Ordered]@{ + BinaryInput=$BinaryInput + SupportedAttributes=$SupportedAttributes + OnLoad = $null + OnSave = $null + } + } +} + + +#region Helpers Add-Type @' public static class Flattener { @@ -1521,6 +1607,7 @@ function GetResultsDirectlyInternal $data['distinguishedName']=$sr.DistinguishedName foreach($attrName in $sr.Attributes.AttributeNames) { + $transform = $script:RegisteredTransforms[$attrName] $BinaryInput = ($null -ne $transform -and $transform.BinaryInput -eq $true) -or ($attrName -in $BinaryProperties) if($null -ne $transform -and $null -ne $transform.OnLoad) @@ -1538,11 +1625,7 @@ function GetResultsDirectlyInternal } } } - #flatten - $coll=@($data.Keys) - foreach($prop in $coll) {$data[$prop] = [Flattener]::FlattenArray($data[$prop])} - #return result to pipeline - [PSCustomObject]$data + $data } #the response may contain paged search response. If so, we will need a cookie from it [System.DirectoryServices.Protocols.PageResultResponseControl] $prrc=$rsp.Controls | Where-Object{$_ -is [System.DirectoryServices.Protocols.PageResultResponseControl]} @@ -1640,11 +1723,7 @@ function GetResultsIndirectlyInternal } } } - #flatten - $coll=@($data.Keys) - foreach($prop in $coll) {$data[$prop] = [Flattener]::FlattenArray($data[$prop])} - #return result to pipeline - [PSCustomObject]$data + $data } #the response may contain paged search response. If so, we will need a cookie from it [System.DirectoryServices.Protocols.PageResultResponseControl] $prrc=$rsp.Controls | Where-Object{$_ -is [System.DirectoryServices.Protocols.PageResultResponseControl]} @@ -1755,11 +1834,7 @@ function GetResultsIndirectlyRangedInternal $data[$attrName] = (& $transform.OnLoad -Values $data[$attrName]) } } - #flatten - $coll=@($data.Keys) - foreach($prop in $coll) {$data[$prop] = [Flattener]::FlattenArray($data[$prop])} - #return result to pipeline - [PSCustomObject]$data + $data } #the response may contain paged search response. If so, we will need a cookie from it [System.DirectoryServices.Protocols.PageResultResponseControl] $prrc=$rsp.Controls | Where-Object{$_ -is [System.DirectoryServices.Protocols.PageResultResponseControl]} @@ -1772,4 +1847,5 @@ function GetResultsIndirectlyRangedInternal } } } -} \ No newline at end of file +} +#endregion diff --git a/TransformTemplate/_Template.ps1 b/TransformTemplate/_Template.ps1 index 5cf644b..8ddcd35 100644 --- a/TransformTemplate/_Template.ps1 +++ b/TransformTemplate/_Template.ps1 @@ -11,17 +11,11 @@ if($FullLoad) # CSharp types added via Add-Type are supported } -#add attributes that can be used with this transform +#add attributes that can be processed by this transform $SupportedAttributes = @() # This is mandatory definition of transform that is expected by transform architecture -$prop=[Ordered]@{ - BinaryInput=$false - SupportedAttributes=$SupportedAttributes - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock = New-LdapAttributeTransformDefinition -SupportedAttributes $SupportedAttributes $codeBlock.OnLoad = { param( [object[]]$Values @@ -50,4 +44,3 @@ $codeBlock.OnSave = { } } $codeBlock - diff --git a/Transforms/Boolean.ps1 b/Transforms/Boolean.ps1 index fd4063a..a3ea4f8 100644 --- a/Transforms/Boolean.ps1 +++ b/Transforms/Boolean.ps1 @@ -16,16 +16,11 @@ if($FullLoad) $SupportedAttributes = @('IsCriticalSystemObject') # This is mandatory definition of transform that is expected by transform architecture -$prop=[Ordered]@{ - BinaryInput=$false - SupportedAttributes=$SupportedAttributes - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes $SupportedAttributes + $codeBlock.OnLoad = { param( - [object[]]$Values + [string[]]$Values ) Process { diff --git a/Transforms/Certificate.ps1 b/Transforms/Certificate.ps1 index 9818915..a1be932 100644 --- a/Transforms/Certificate.ps1 +++ b/Transforms/Certificate.ps1 @@ -5,16 +5,10 @@ param ( $FullLoad ) -$prop=[Ordered]@{ - BinaryInput=$true - SupportedAttributes=@('userCertificate','userCert') - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('userCertificate','userCert') -BinaryInput $codeBlock.OnLoad = { param( - [object[]]$Values + [byte[][]]$Values ) Process { diff --git a/Transforms/GeneralizedTime.ps1 b/Transforms/GeneralizedTime.ps1 index 0737b04..969a9f9 100644 --- a/Transforms/GeneralizedTime.ps1 +++ b/Transforms/GeneralizedTime.ps1 @@ -11,50 +11,11 @@ if($FullLoad) # Add any types that are used by transforms # CSharp types added via Add-Type are supported } +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('createTimestamp','dsCorePropagationData','modifyTimestamp','whenCreated','whenChanged','msExchWhenMailboxCreated') -New-Object PSCustomObject -property ([ordered]@{ - SupportedAttributes=@('createTimestamp','dsCorePropagationData','modifyTimestamp','whenCreated','whenChanged','msExchWhenMailboxCreated') - OnLoad = { - param( - [object[]]$Values - ) - Process - { - foreach($Value in $Values) - { - [DateTime]::ParseExact($value,'yyyyMMddHHmmss.fK',[System.Globalization.CultureInfo]::InvariantCulture,[System.Globalization.DateTimeStyles]::AssumeUniversal) - } - } - } - OnSave = { - param( - [DateTime[]]$Values - ) - - Process - { - foreach($Value in $Values) - { - $value.ToUniversalTime().ToString('yyyyMMddHHmmss.0Z') - } - } - } -}) - -<# -#add attributes that can be used with this transform -$SupportedAttributes = @('whenCreated','whenChanged') - -# This is mandatory definition of transform that is expected by transform architecture -$prop=[Ordered]@{ - SupportedAttributes=$SupportedAttributes - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop $codeBlock.OnLoad = { param( - [object[]]$Values + [string[]]$Values ) Process { @@ -64,6 +25,7 @@ $codeBlock.OnLoad = { } } } + $codeBlock.OnSave = { param( [DateTime[]]$Values @@ -77,5 +39,6 @@ $codeBlock.OnSave = { } } } + $codeBlock -#> + diff --git a/Transforms/Integer.ps1 b/Transforms/Integer.ps1 index ce6295f..cd33394 100644 --- a/Transforms/Integer.ps1 +++ b/Transforms/Integer.ps1 @@ -16,13 +16,8 @@ if($FullLoad) $SupportedAttributes = @('codePage','countryCode','logonCount','msDS-Approx-Immed-Subordinates','ms-DS-KeyVersionNumber','ms-DS-ManagedPasswordInterval') # This is mandatory definition of transform that is expected by transform architecture -$prop=[Ordered]@{ - BinaryInput=$false - SupportedAttributes=$SupportedAttributes - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes $SupportedAttributes + $codeBlock.OnLoad = { param( [string[]]$Values diff --git a/Transforms/Long.ps1 b/Transforms/Long.ps1 index b49fde5..cdff899 100644 --- a/Transforms/Long.ps1 +++ b/Transforms/Long.ps1 @@ -16,13 +16,7 @@ if($FullLoad) $SupportedAttributes = @('uSNChanged', 'uSNCreated') # This is mandatory definition of transform that is expected by transform architecture -$prop=[Ordered]@{ - BinaryInput=$false - SupportedAttributes=$SupportedAttributes - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes $SupportedAttributes $codeBlock.OnLoad = { param( [string[]]$Values diff --git a/Transforms/ProxyAddress.ps1 b/Transforms/ProxyAddress.ps1 index 7a71976..f66d3b2 100644 --- a/Transforms/ProxyAddress.ps1 +++ b/Transforms/ProxyAddress.ps1 @@ -63,15 +63,11 @@ public class ProxyAddress:IEquatable '@ } -$prop=[Ordered]@{ - SupportedAttributes=@('proxyAddresses','targetAddress') - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('proxyAddresses','targetAddress') + $codeBlock.OnLoad = { param( - [object[]]$Values + [string[]]$Values ) Process { diff --git a/Transforms/SamAccountType.ps1 b/Transforms/SamAccountType.ps1 index b9fd7e9..bbd456f 100644 --- a/Transforms/SamAccountType.ps1 +++ b/Transforms/SamAccountType.ps1 @@ -27,22 +27,17 @@ public enum SamAccountType } '@ } +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('sAMAccountType') -$prop=[Ordered]@{ - SupportedAttributes=@('sAMAccountType') - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop $codeBlock.OnLoad = { param( - [int[]]$Values + [string[]]$Values ) Process { foreach($Value in $Values) { - [SamAccountType].GetEnumValues().ForEach({if($Value -eq $_) {"$_"}}) + [SamAccountType].GetEnumValues().ForEach({if([int]$Value -eq $_) {"$_"}}) } } } diff --git a/Transforms/WindowsHelloKeyInfo.ps1 b/Transforms/WindowsHelloKeyInfo.ps1 index 13e4af3..44811a0 100644 --- a/Transforms/WindowsHelloKeyInfo.ps1 +++ b/Transforms/WindowsHelloKeyInfo.ps1 @@ -272,15 +272,11 @@ public class KeyCredentialInfo $SupportedAttributes = @('msDs-KeyCredentialLink') # This is mandatory definition of transform that is expected by transform architecture -$prop=[Ordered]@{ - SupportedAttributes=$SupportedAttributes - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes $SupportedAttributes + $codeBlock.OnLoad = { param( - [object[]]$Values + [string[]]$Values ) Process { diff --git a/Transforms/admpwd.e.pwd.ps1 b/Transforms/admpwd.e.pwd.ps1 index bff0a0a..6c42555 100644 --- a/Transforms/admpwd.e.pwd.ps1 +++ b/Transforms/admpwd.e.pwd.ps1 @@ -48,13 +48,8 @@ public class AdmPwdPassword $SupportedAttributes = @('ms-Mcs-AdmPwd') # This is mandatory definition of transform that is expected by transform architecture -$prop=[Ordered]@{ - BinaryInput=$false - SupportedAttributes=$SupportedAttributes - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes $SupportedAttributes + $codeBlock.OnLoad = { param( [string[]]$Values diff --git a/Transforms/admpwd.e.pwdhistory.ps1 b/Transforms/admpwd.e.pwdhistory.ps1 index 1f0b25b..228ec2f 100644 --- a/Transforms/admpwd.e.pwdhistory.ps1 +++ b/Transforms/admpwd.e.pwdhistory.ps1 @@ -60,13 +60,8 @@ public class AdmPwdPasswordHistory $SupportedAttributes = @('ms-Mcs-AdmPwdHistory') # This is mandatory definition of transform that is expected by transform architecture -$prop=[Ordered]@{ - BinaryInput=$false - SupportedAttributes=$SupportedAttributes - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes $SupportedAttributes + $codeBlock.OnLoad = { param( [string[]]$Values diff --git a/Transforms/fileTime.ps1 b/Transforms/fileTime.ps1 index 2f6aeed..811665d 100644 --- a/Transforms/fileTime.ps1 +++ b/Transforms/fileTime.ps1 @@ -5,22 +5,18 @@ param ( $FullLoad ) -$prop=[Ordered]@{ - SupportedAttributes=@('accountExpires','badPasswordTime','lastLogon','lastLogonTimestamp','ms-Mcs-AdmPwdExpirationTime','msDS-UserPasswordExpiryTimeComputed','pwdLastSet') - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('accountExpires','badPasswordTime','lastLogon','lastLogonTimestamp','ms-Mcs-AdmPwdExpirationTime','msDS-UserPasswordExpiryTimeComputed','pwdLastSet') + $codeBlock.OnLoad = { param( - [long[]]$Values + [string[]]$Values ) Process { foreach($Value in $Values) { try { - [DateTime]::FromFileTimeUtc($Value) + [DateTime]::FromFileTimeUtc([long]$Value) } catch { #value outside of range for filetime @@ -32,14 +28,22 @@ $codeBlock.OnLoad = { } $codeBlock.OnSave = { param( - [DateTime[]]$Values + [Object[]]$Values ) Process { foreach($Value in $Values) { - $Value.ToFileTimeUtc() + #standard expiration + if($value -is [datetime]) { + $Value.ToFileTimeUtc() + continue; + } + #values that did not transform to DateTime in OnLoad + if($value -is [string]) { + $value + } } } } diff --git a/Transforms/groupType.ps1 b/Transforms/groupType.ps1 index c05b492..a6f0bc1 100644 --- a/Transforms/groupType.ps1 +++ b/Transforms/groupType.ps1 @@ -11,40 +11,36 @@ if($FullLoad) Add-Type @' using System; [Flags] -public enum GroupType +public enum GroupType: uint { Global = 0x00000002, Local = 0x00000004, Universal = 0x00000008, - Security = unchecked((int)0x80000000) + Security = 0x80000000 } '@ } -$prop=[Ordered]@{ - SupportedAttributes=@('groupType') - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('groupType') + $codeBlock.OnLoad = { param( - [int[]]$Values + [string[]]$Values ) Process { - [GroupType].GetEnumValues().ForEach({if(($Value -band $_) -eq $_) {"$_"}}) + [GroupType].GetEnumValues().ForEach({if(([uint]$Value -band $_) -eq $_) {"$_"}}) } } $codeBlock.OnSave = { param( - [System.String[]]$Values + [GroupType[]]$Values ) Process { $retVal = 0 - $Values.ForEach({ [GroupType]$val=$_; $retVal+=$val}) + $Values.ForEach({ $retVal+=$_}) $retVal } } diff --git a/Transforms/guid.ps1 b/Transforms/guid.ps1 index 77b1a5f..6bfee1b 100644 --- a/Transforms/guid.ps1 +++ b/Transforms/guid.ps1 @@ -9,13 +9,8 @@ if($FullLoad) { } -$prop=[Ordered]@{ - BinaryInput=$true - SupportedAttributes=@('objectGuid','mS-DS-ConsistencyGuid','msExchMailboxGuid','msExchPoliciesExcluded') - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('objectGuid','mS-DS-ConsistencyGuid','msExchMailboxGuid','msExchPoliciesExcluded') -BinaryInput + $codeBlock.OnLoad = { param( [byte[][]]$Values diff --git a/Transforms/quotaUsage.ps1 b/Transforms/quotaUsage.ps1 index 8f676ef..1a4036e 100644 --- a/Transforms/quotaUsage.ps1 +++ b/Transforms/quotaUsage.ps1 @@ -6,11 +6,7 @@ param ( $FullLoad ) -$codeBlock=[PSCustomObject][Ordered]@{ - SupportedAttributes=@('msDS-TopQuotaUsage') - OnLoad = $null - OnSave = $null -} +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('msDS-TopQuotaUsage') $codeBlock.OnLoad = { param( @@ -26,7 +22,7 @@ $codeBlock.OnLoad = { } $codeBlock.OnSave = { param( - [object[]]$Values + [System.Xml.XmlDocument[]]$Values ) Process diff --git a/Transforms/replicationMetadata.ps1 b/Transforms/replicationMetadata.ps1 index 32ba5a9..fd0a461 100644 --- a/Transforms/replicationMetadata.ps1 +++ b/Transforms/replicationMetadata.ps1 @@ -6,17 +6,11 @@ param ( $FullLoad ) -$prop=[Ordered]@{ - SupportedAttributes=@('msDS-ReplAttributeMetaData','msDS-ReplValueMetaData','msDS-NCReplCursors','msDS-NCReplInboundNeighbors') - OnLoad = $null - OnSave = $null -} - -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('msDS-ReplAttributeMetaData','msDS-ReplValueMetaData','msDS-NCReplCursors','msDS-NCReplInboundNeighbors') $codeBlock.OnLoad = { param( - [object[]]$Values + [string[]]$Values ) Process { @@ -28,7 +22,7 @@ $codeBlock.OnLoad = { } $codeBlock.OnSave = { param( - [object[]]$Values + [System.Xml.XmlDocument[]]$Values ) Process diff --git a/Transforms/searchFlags.ps1 b/Transforms/searchFlags.ps1 index 9db3b1d..0f63998 100644 --- a/Transforms/searchFlags.ps1 +++ b/Transforms/searchFlags.ps1 @@ -30,15 +30,11 @@ public enum SearchFlags } '@ } +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('searchFlags') -$codeBlock=[PSCustomObject][Ordered]@{ - SupportedAttributes=@('searchFlags') - OnLoad = $null - OnSave = $null -} $codeBlock.OnLoad = { param( - [object[]]$Values + [string[]]$Values ) Process { @@ -50,13 +46,13 @@ $codeBlock.OnLoad = { } $codeBlock.OnSave = { param( - [object[]]$Values + [SearchFlags[]]$Values ) Process { $retVal = 0 - $Values.ForEach({ [SearchFlags]$val=$_; $retVal+=$val}) + $Values.ForEach({ $retVal+=$_}) $retVal } diff --git a/Transforms/securityDescriptor.ps1 b/Transforms/securityDescriptor.ps1 index aa81db5..563146e 100644 --- a/Transforms/securityDescriptor.ps1 +++ b/Transforms/securityDescriptor.ps1 @@ -8,14 +8,8 @@ param ( if($FullLoad) { } +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('msDS-AllowedToActOnBehalfOfOtherIdentity','ms-DS-GroupMSAMembership','ntSecurityDescriptor') -BinaryInput -$prop=[Ordered]@{ - BinaryInput=$true - SupportedAttributes=@('msDS-AllowedToActOnBehalfOfOtherIdentity','ms-DS-GroupMSAMembership','ntSecurityDescriptor') - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop $codeBlock.OnLoad = { param( [byte[][]]$Values diff --git a/Transforms/securityIdentifier.ps1 b/Transforms/securityIdentifier.ps1 index 589e64d..b4ce7ec 100644 --- a/Transforms/securityIdentifier.ps1 +++ b/Transforms/securityIdentifier.ps1 @@ -4,14 +4,8 @@ param ( [Switch] $FullLoad ) +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('objectSid','tokenGroups','tokenGroupsGlobalAndUniversal','tokenGroupsNoGCAcceptable') -BinaryInput -$prop=[Ordered]@{ - BinaryInput=$true - SupportedAttributes=@('objectSid','tokenGroups','tokenGroupsGlobalAndUniversal','tokenGroupsNoGCAcceptable') - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop $codeBlock.OnLoad = { param( [byte[][]]$Values diff --git a/Transforms/supportedEncryptionTypes.ps1 b/Transforms/supportedEncryptionTypes.ps1 index 57932e2..924a038 100644 --- a/Transforms/supportedEncryptionTypes.ps1 +++ b/Transforms/supportedEncryptionTypes.ps1 @@ -20,22 +20,17 @@ public enum EncryptionTypes } '@ } +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('msDS-SupportedEncryptionTypes') -$prop=[Ordered]@{ - SupportedAttributes=@('msDS-SupportedEncryptionTypes') - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop $codeBlock.OnLoad = { param( - [object[]]$Values + [string[]]$Values ) Process { foreach($Value in $Values) { - [EncryptionTypes].GetEnumValues().ForEach({if(($Value -band $_) -eq $_) {"$_"}}) + [EncryptionTypes].GetEnumValues().ForEach({if(([int]$Value -band $_) -eq $_) {"$_"}}) } } } diff --git a/Transforms/systemFlags.ps1 b/Transforms/systemFlags.ps1 index 5a43a1b..94b41f7 100644 --- a/Transforms/systemFlags.ps1 +++ b/Transforms/systemFlags.ps1 @@ -30,21 +30,16 @@ public enum SystemFlags : uint } '@ } - -$codeBlock=[PSCustomObject][Ordered]@{ - SupportedAttributes=@('systemFlags') - OnLoad = $null - OnSave = $null -} +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('systemFlags') $codeBlock.OnLoad = { param( - [object[]]$Values + [string[]]$Values ) Process { foreach($Value in $Values) { - [SystemFlags].GetEnumValues().ForEach({if(($Value -band $_) -eq $_) {"$_"}}) + [SystemFlags].GetEnumValues().ForEach({if(([uint]$Value -band $_) -eq $_) {"$_"}}) } } } diff --git a/Transforms/unicodePwd.ps1 b/Transforms/unicodePwd.ps1 index 248c9df..4b0e681 100644 --- a/Transforms/unicodePwd.ps1 +++ b/Transforms/unicodePwd.ps1 @@ -4,13 +4,8 @@ param ( [Switch] $FullLoad ) +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('unicodePwd') -BinaryInput -$prop=[Ordered]@{ - SupportedAttributes=@('unicodePwd') - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop $codeBlock.OnSave = { param( [string[]]$Values diff --git a/Transforms/userAccountControl.ps1 b/Transforms/userAccountControl.ps1 index 0799db1..cf6402c 100644 --- a/Transforms/userAccountControl.ps1 +++ b/Transforms/userAccountControl.ps1 @@ -12,7 +12,7 @@ if($FullLoad) Add-Type @' using System; [Flags] -public enum UserAccountControl +public enum UserAccountControl : uint { UF_SCRIPT = 0x1, UF_ACCOUNTDISABLE = 0x2, @@ -41,22 +41,17 @@ public enum UserAccountControl } '@ } +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes @('userAccountControl') -$prop=[Ordered]@{ - SupportedAttributes=@('userAccountControl') - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop $codeBlock.OnLoad = { param( - [object[]]$Values + [string[]]$Values ) Process { foreach($Value in $Values) { - [UserAccountControl].GetEnumValues().ForEach({if(($Value -band $_) -eq $_) {"$_"}}) + [UserAccountControl].GetEnumValues().ForEach({if(([UInt32]$Value -band $_) -eq $_) {"$_"}}) } } } diff --git a/Transforms/wellKnownObject.ps1 b/Transforms/wellKnownObject.ps1 index ecdbb8c..29af816 100644 --- a/Transforms/wellKnownObject.ps1 +++ b/Transforms/wellKnownObject.ps1 @@ -47,15 +47,11 @@ if($FullLoad) $SupportedAttributes = @('wellKnownObjects','otherWellKnownObjects') # This is mandatory definition of transform that is expected by transform architecture -$prop=[Ordered]@{ - SupportedAttributes=$SupportedAttributes - OnLoad = $null - OnSave = $null -} -$codeBlock = new-object PSCustomObject -property $prop +$codeBlock= New-LdapAttributeTransformDefinition -SupportedAttributes $SupportedAttributes + $codeBlock.OnLoad = { param( - [object[]]$Values + [string[]]$Values ) Process {