AADConnectDsc.psm1
#Region '.\Prefix.ps1' -1 $script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/DscResource.Common' Import-Module -Name $script:dscResourceCommonModulePath #EndRegion '.\Prefix.ps1' 3 #Region '.\Enum\AttributeMappingFlowType.ps1' -1 enum AttributeMappingFlowType { Direct Constant Expression } #EndRegion '.\Enum\AttributeMappingFlowType.ps1' 7 #Region '.\Enum\AttributeValueMergeType.ps1' -1 enum AttributeValueMergeType { Update Replace MergeCaseInsensitive Merge } #EndRegion '.\Enum\AttributeValueMergeType.ps1' 8 #Region '.\Enum\ComparisonOperator.ps1' -1 enum ComparisonOperator { EQUAL NOTEQUAL LESSTHAN LESSTHAN_OR_EQUAL CONTAINS NOTCONTAINS STARTSWITH NOTSTARTSWITH ENDSWITH NOTENDSWITH GREATERTHAN GREATERTHAN_OR_EQUAL ISNULL ISNOTNULL ISIN ISNOTIN ISBITSET ISBITNOTSET ISMEMBEROF ISNOTMEMBEROF } #EndRegion '.\Enum\ComparisonOperator.ps1' 23 #Region '.\Enum\Ensure.ps1' -1 enum Ensure { Absent Present Unknown } #EndRegion '.\Enum\Ensure.ps1' 6 #Region '.\Classes\AADConnectDirectoryExtensionAttribute.ps1' -1 [DscResource()] class AADConnectDirectoryExtensionAttribute { [DscProperty(Key = $true)] [string]$Name [DscProperty(Key = $true)] [string]$AssignedObjectClass [DscProperty(Mandatory = $true)] [string]$Type [DscProperty(Mandatory = $true)] [bool]$IsEnabled [DscProperty()] [Ensure]$Ensure AADConnectDirectoryExtensionAttribute() { $this.Ensure = 'Present' } [bool]Test() { $currentState = Convert-ObjectToHashtable -Object $this.Get() $desiredState = Convert-ObjectToHashtable -Object $this if ($currentState.Ensure -ne $desiredState.Ensure) { return $false } if ($desiredState.Ensure -eq [Ensure]::Absent) { return $true } $compare = Test-DscParameterState -CurrentValues $currentState -DesiredValues $desiredState -TurnOffTypeChecking -SortArrayValues return $compare } [AADConnectDirectoryExtensionAttribute]Get() { $currentState = [AADConnectDirectoryExtensionAttribute]::new() $attribute = Get-AADConnectDirectoryExtensionAttribute -Name $this.Name -ErrorAction SilentlyContinue | Where-Object { $_.AssignedObjectClass -eq $this.AssignedObjectClass -and $_.Type -eq $this.Type } $currentState.Ensure = [Ensure][int][bool]$attribute $currentState.Name = $this.Name $currentState.AssignedObjectClass = $this.AssignedObjectClass $currentState.Type = $attribute.Type $currentState.IsEnabled = $attribute.IsEnabled return $currentState } [void]Set() { $param = Convert-ObjectToHashtable $this if ($this.Ensure -eq 'Present') { $cmdet = Get-Command -Name Add-AADConnectDirectoryExtensionAttribute $param = Sync-Parameter -Command $cmdet -Parameters $param Add-AADConnectDirectoryExtensionAttribute @param -Force } else { $cmdet = Get-Command -Name Remove-AADConnectDirectoryExtensionAttribute $param = Sync-Parameter -Command $cmdet -Parameters $param Remove-AADConnectDirectoryExtensionAttribute @param } } } #EndRegion '.\Classes\AADConnectDirectoryExtensionAttribute.ps1' 78 #Region '.\Classes\AADSyncRule.ps1' -1 [DscResource()] class AADSyncRule { [DscProperty(Key = $true)] [string]$Name [DscProperty()] [string]$Description [DscProperty()] [bool]$Disabled [DscProperty(NotConfigurable)] [string]$Identifier [DscProperty(NotConfigurable)] [string]$Version [DscProperty()] [ScopeConditionGroup[]]$ScopeFilter [DscProperty()] [JoinConditionGroup[]]$JoinFilter [DscProperty()] [AttributeFlowMapping[]]$AttributeFlowMappings [DscProperty(Key = $true)] [string]$ConnectorName [DscProperty(NotConfigurable)] [string]$Connector [DscProperty()] [int]$Precedence [DscProperty()] [string]$PrecedenceAfter [DscProperty()] [string]$PrecedenceBefore [DscProperty(Mandatory = $true)] [string]$TargetObjectType [DscProperty(Mandatory = $true)] [string]$SourceObjectType [DscProperty(Mandatory = $true)] [string]$Direction [DscProperty(Mandatory = $true)] [string]$LinkType [DscProperty()] [bool]$EnablePasswordSync [DscProperty()] [string]$ImmutableTag [DscProperty()] [bool]$IsStandardRule [DscProperty(NotConfigurable)] [bool]$IsLegacyCustomRule [DscProperty()] [Ensure]$Ensure AADSyncRule() { $this.Ensure = 'Present' } [bool]Test() { $currentState = $this.Get() | ConvertTo-Yaml | ConvertFrom-Yaml $desiredState = $this | ConvertTo-Yaml | ConvertFrom-Yaml #Remove all whitespace from expressions in AttributeFlowMappings, otherwise they will not match due to encoding differences foreach ($afm in $currentState.AttributeFlowMappings) { if (-not [string]::IsNullOrEmpty($afm.Expression)) { $afm.Expression = $afm.Expression -replace '\s', '' } } foreach ($afm in $desiredState.AttributeFlowMappings) { if (-not [string]::IsNullOrEmpty($afm.Expression)) { $afm.Expression = $afm.Expression -replace '\s', '' } } $param = @{ CurrentValues = $currentState DesiredValues = $desiredState TurnOffTypeChecking = $true SortArrayValues = $true } $param.ExcludeProperties = if ($this.IsStandardRule) { ($this | Get-Member -MemberType Property).Name | Where-Object { $_ -notin 'Name', 'Disabled' } } else { 'Connector', 'Version', 'Identifier' } $compare = if ($currentState.Ensure -eq $desiredState.Ensure) { if ($desiredState.Ensure -eq 'Present') { Write-Verbose "The sync rule '$($this.Name)' exists and should exist, comparing rule with 'Test-DscParameterState'." Test-DscParameterState @param -ReverseCheck if ($this.IsStandardRule) { $param.ExcludeProperties = 'Connector', 'Version', 'Identifier', 'Precedence' Write-Verbose '-----------------------------------------------------------------------------------------------------' Write-Verbose '--------------------------- Comparing all properties for standard rule ------------------------------' Write-Verbose '----------------------- The result will not effect the overall test result --------------------------' Write-Verbose '-----------------------------------------------------------------------------------------------------' $null = Test-DscParameterState @param -ReverseCheck Write-Verbose '-----------------------------------------------------------------------------------------------------' Write-Verbose '-----------------------------------------------------------------------------------------------------' } } else { Write-Verbose "The sync rule '$($this.Name)' is absent and should be absent." $true } } else { if ($desiredState.Ensure -eq 'Present') { Write-Verbose "The sync rule '$($this.Name)' for connector '$($this.ConnectorName)' is absent, but should be present." } else { Write-Verbose "The sync rule '$($this.Name)' for connector '$($this.ConnectorName)' is present, but should be absent." } $false } return $compare } [AADSyncRule]Get() { $syncRule = Get-ADSyncRule -Name $this.Name -ConnectorName $this.ConnectorName $currentState = [AADSyncRule]::new() $currentState.Name = $this.Name if ($syncRule.Count -gt 1) { Write-Error "There is more than one sync rule with the name '$($this.Name)'." $currentState.Ensure = 'Unknown' return $currentState } $currentState.Ensure = [Ensure][int][bool]$syncRule $currentState.ConnectorName = (Get-ADSyncConnector | Where-Object Identifier -EQ $syncRule.Connector).Name $currentState.Connector = $syncRule.Connector $currentState.Description = $syncRule.Description $currentState.Disabled = $syncRule.Disabled $currentState.Direction = $syncRule.Direction $currentState.EnablePasswordSync = $syncRule.EnablePasswordSync $currentState.Identifier = $syncRule.Identifier $currentState.LinkType = $syncRule.LinkType $currentState.Precedence = $syncRule.Precedence $currentState.ScopeFilter = @() foreach ($scg in $syncRule.ScopeFilter) { $scg2 = [ScopeConditionGroup]::new() foreach ($sc in $scg.ScopeConditionList) { $sc2 = [ScopeCondition]::new($sc.Attribute, $sc.ComparisonValue, $sc.ComparisonOperator) $scg2.ScopeConditionList += $sc2 } $currentState.ScopeFilter += $scg2 } $currentState.JoinFilter = @() foreach ($jcg in $syncRule.JoinFilter) { $jcg2 = [JoinConditionGroup]::new() foreach ($jc in $jcg.JoinConditionList) { $jc2 = [JoinCondition]::new($jc.CSAttribute, $jc.MVAttribute, $jc.CaseSensitive) $jcg2.JoinConditionList += $jc2 } $currentState.JoinFilter += $jcg2 } $currentState.AttributeFlowMappings = @() foreach ($af in $syncRule.AttributeFlowMappings) { $af2 = [AttributeFlowMapping]::new() $af2.Source = $af.Source[0] $af2.Destination = $af.Destination $af2.ExecuteOnce = $af.ExecuteOnce $af2.FlowType = $af.FlowType $af2.ValueMergeType = $af.ValueMergeType if ($null -eq $af.Expression) { $af2.Expression = '' } else { $af2.Expression = $af.Expression } $currentState.AttributeFlowMappings += $af2 } $currentState.SourceObjectType = $syncRule.SourceObjectType $currentState.TargetObjectType = $syncRule.TargetObjectType $currentState.Version = $syncRule.Version $currentState.IsStandardRule = $syncRule.IsStandardRule $currentState.IsLegacyCustomRule = $syncRule.IsLegacyCustomRule return $currentState } [void]Set() { $connectorObject = Get-ADSyncConnector -Name $this.ConnectorName -ErrorAction SilentlyContinue if ($null -eq $connectorObject) { Write-Error "The connector '$($this.ConnectorName)' does not exist." return } $this.Connector = $connectorObject.Identifier Write-Verbose "Got connector '$($this.ConnectorName)' for rule '$($this.Name)' with identifier '$($this.Connector)'." $existingRule = Get-ADSyncRule -Name $this.Name -ConnectorName $this.ConnectorName if ($existingRule) { Write-Verbose "Got existing rule '$($existingRule.Name)' with identifier '$($existingRule.Identifier)' for connector '$($this.ConnectorName)'." $this.Identifier = $existingRule.Identifier } else { $this.Identifier = New-Guid2 -InputString "$($this.Name)$($this.ConnectorName)" Write-Verbose "No existing rule found with the name '$($this.Name)'. Using identifier '$($this.Identifier)'." } $desiredState = Convert-ObjectToHashtable -Object $this if ($this.Ensure -eq 'Present') { Write-Verbose "The sync rule '$($this.Name)' should be present for connector '$($this.ConnectorName)'. Proceeding with creation or update." if ($this.IsStandardRule) { if ($null -eq $existingRule) { Write-Error "A Sync Rule defined as 'IsStandardRule' does not exist. It cannot be enabled or disabled." return } Write-Warning "The only property that will be changed on a standard rule is 'Disabled'. All other configuration drifts will not be corrected." $existingRule.Disabled = $this.Disabled Write-Verbose "Setting the 'Disabled' property of the rule '$($this.Name)' to '$($this.Disabled)' and calling 'Add-ADSyncRule'." $existingRule | Add-ADSyncRule } else { if ($existingRule.IsStandardRule) { Write-Error 'It is not allowed to modify a standard rule. It can only be enabled or disabled.' return } $cmdet = Get-Command -Name New-ADSyncRule $param = Sync-Parameter -Command $cmdet -Parameters $desiredState $rule = New-ADSyncRule @param if ($this.ScopeFilter) { $i = 0 foreach ($scg in $this.ScopeFilter) { Write-Verbose "Processing ScopeConditionList $i" $scopeConditions = foreach ($sc in $scg.ScopeConditionList) { Write-Verbose "Processing ScopeFilter: Attribute = '$($sc.Attribute)', ComparisonValue = '$($sc.ComparisonValue)', ComparisonOperator = '$($sc.ComparisonOperator)'" [Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition]::new($sc.Attribute, $sc.ComparisonValue, $sc.ComparisonOperator) } Write-Verbose "ScopeConditionList count is $($scopeConditions.Count)" $rule | Add-ADSyncScopeConditionGroup -ScopeConditions $scopeConditions $i++ } } if ($this.JoinFilter) { $i = 0 foreach ($jcg in $this.JoinFilter) { Write-Verbose "Processing JoinConditionList $i" $joinConditions = foreach ($jc in $jcg.JoinConditionList) { Write-Verbose "Processing JoinFilter: CSAttribute = '$($jc.CSAttribute)', MVAttribute = '$($jc.MVAttribute)', CaseSensitive = '$($jc.CaseSensitive)'" [Microsoft.IdentityManagement.PowerShell.ObjectModel.JoinCondition]::new($jc.CSAttribute, $jc.MVAttribute, $jc.CaseSensitive) } Write-Verbose "JoinConditionList count is $($joinConditions.Count)" $rule | Add-ADSyncJoinConditionGroup -JoinConditions $joinConditions } } if ($this.AttributeFlowMappings) { $i = 0 foreach ($af in $this.AttributeFlowMappings) { Write-Verbose "Processing AttributeFlowMapping $i, Source = '$($af.Source)', Destination = '$($af.Destination)', Expression = '$($af.Expression)'" $afHashTable = Convert-ObjectToHashtable -Object $af $param = Sync-Parameter -Command (Get-Command -Name Add-ADSyncAttributeFlowMapping) -Parameters $afHashTable $param.SynchronizationRule = $rule if ([string]::IsNullOrEmpty($param.Expression)) { $param.Remove('Expression') } if ([string]::IsNullOrEmpty($param.Source)) { $param.Remove('Source') } Add-ADSyncAttributeFlowMapping @param } } Write-Verbose "Calling 'Add-ADSyncRule' to create or update the rule '$($this.Name)'." $rule | Add-ADSyncRule } } else { if ($existingRule) { Remove-ADSyncRule -Identifier $this.Identifier } } } } #EndRegion '.\Classes\AADSyncRule.ps1' 364 #Region '.\Classes\AttributeFlowMapping.ps1' -1 class AttributeFlowMapping { AttributeFlowMapping() { } [DscProperty(Key)] [string]$Destination [DscProperty()] [bool]$ExecuteOnce [DscProperty(Key)] [string]$Expression [DscProperty(Key)] [AttributeMappingFlowType]$FlowType [DscProperty(NotConfigurable)] [string]$MappingSourceAsString [DscProperty(Key)] [string]$Source [DscProperty()] [AttributeValueMergeType]$ValueMergeType } #EndRegion '.\Classes\AttributeFlowMapping.ps1' 28 #Region '.\Classes\JoinCondition.ps1' -1 class JoinCondition { [DscProperty()] [string]$CSAttribute [DscProperty()] [string]$MVAttribute [DscProperty()] [bool]$CaseSensitive JoinCondition() { } JoinCondition([string]$CSAttribute, [string]$MVAttribute, [bool]$CaseSensitive) { $this.CSAttribute = $CSAttribute $this.MVAttribute = $MVAttribute $this.CaseSensitive = $CaseSensitive } } #EndRegion '.\Classes\JoinCondition.ps1' 23 #Region '.\Classes\JoinConditionGroup.ps1' -1 class JoinConditionGroup { [DscProperty()] [JoinCondition[]]$JoinConditionList ScopeConditionGroup() { } } #EndRegion '.\Classes\JoinConditionGroup.ps1' 11 #Region '.\Classes\ScopeCondition.ps1' -1 class ScopeCondition { [DscProperty()] [string]$Attribute [DscProperty()] [string]$ComparisonValue [DscProperty()] [ComparisonOperator]$ComparisonOperator ScopeCondition() { } ScopeCondition([hashtable]$Definition) { $this.Attribute = $Definition['Attribute'] $this.ComparisonValue = $Definition['ComparisonValue'] $this.ComparisonOperator = $Definition['ComparisonOperator'] } ScopeCondition([string]$Attribute, [string]$ComparisonValue, [string]$ComparisonOperator) { $this.Attribute = $Attribute $this.ComparisonValue = $ComparisonValue $this.ComparisonOperator = $ComparisonOperator } } #EndRegion '.\Classes\ScopeCondition.ps1' 30 #Region '.\Classes\ScopeConditionGroup.ps1' -1 class ScopeConditionGroup { [DscProperty()] [ScopeCondition[]]$ScopeConditionList ScopeConditionGroup() { } } #EndRegion '.\Classes\ScopeConditionGroup.ps1' 11 #Region '.\Private\New-Guid2.ps1' -1 function New-Guid2 { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $InputString ) $md5 = [System.Security.Cryptography.MD5]::Create() $hash = $md5.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($InputString)) return [System.Guid]::new($hash).Guid } #EndRegion '.\Private\New-Guid2.ps1' 14 #Region '.\Public\Add-AADConnectDirectoryExtensionAttribute.ps1' -1 <# .SYNOPSIS Adds a directory extension attribute to Azure AD Connect configuration. .DESCRIPTION The Add-AADConnectDirectoryExtensionAttribute function adds a new directory extension attribute to the Azure AD Connect global settings. Directory extension attributes allow you to extend the schema of Azure AD objects with custom attributes that can be synchronized from on-premises Active Directory. This function supports two parameter sets: specifying individual properties or providing a complete attribute string. It includes validation and conflict resolution capabilities. This function requires Windows PowerShell 5.1 and does not work with PowerShell 7. .PARAMETER Name Specifies the name of the directory extension attribute to add. The name should be unique within the object class and follow Azure AD naming conventions. .PARAMETER Type Specifies the data type of the directory extension attribute. Common types include: - String: Text data - Integer: Numeric data - Boolean: True/False values - DateTime: Date and time values .PARAMETER AssignedObjectClass Specifies the object class to which this attribute will be assigned. Common values include: - user: For user objects - group: For group objects - contact: For contact objects - device: For device objects .PARAMETER IsEnabled Specifies whether the directory extension attribute is enabled for synchronization. Set to $true to enable or $false to disable. .PARAMETER FullAttributeString Specifies a complete attribute definition string in the format: "attributeName.objectClass.dataType.enabledStatus" For example: "employeeNumber.user.String.True" .PARAMETER Force Forces the addition of the attribute even if a conflicting attribute with the same name but different type exists. When specified, the existing conflicting attribute is removed. .EXAMPLE Add-AADConnectDirectoryExtensionAttribute -Name "employeeNumber" -Type "String" -AssignedObjectClass "user" -IsEnabled $true Adds an employee number attribute for user objects as a string type. .EXAMPLE Add-AADConnectDirectoryExtensionAttribute -FullAttributeString "departmentCode.user.String.True" Adds a department code attribute using the full attribute string format. .EXAMPLE Add-AADConnectDirectoryExtensionAttribute -Name "badgeNumber" -Type "Integer" -AssignedObjectClass "user" -IsEnabled $true -Force Adds a badge number attribute, replacing any existing conflicting attribute with the same name. .EXAMPLE Get-Content "attributes.txt" | ForEach-Object { Add-AADConnectDirectoryExtensionAttribute -FullAttributeString $_ } Adds multiple attributes from a text file, with each line containing a full attribute string. .INPUTS String. You can pipe attribute strings to this function when using the FullAttributeString parameter. .OUTPUTS None. This function does not return objects but modifies Azure AD Connect global settings. .NOTES - This function requires Windows PowerShell 5.1 and does not work with PowerShell 7 - Requires Azure AD Connect to be installed and the ADSync module to be available - Changes take effect immediately but may require synchronization cycle restart - Use Get-AADConnectDirectoryExtensionAttribute to verify the attribute was added successfully - Directory extension attributes are permanent once synchronized to Azure AD .LINK https://docs.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-sync-feature-directory-extensions .COMPONENT AADConnectDsc .FUNCTIONALITY Azure AD Connect Directory Extension Attribute Management #> function Add-AADConnectDirectoryExtensionAttribute { [CmdletBinding(DefaultParameterSetName = 'ByProperties')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByProperties')] [string]$Name, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByProperties')] [string]$Type, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByProperties')] [string]$AssignedObjectClass, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByProperties')] [bool]$IsEnabled, [Parameter(Mandatory = $true, ParameterSetName = 'SingleObject')] [string]$FullAttributeString, [Parameter()] [switch]$Force ) process { $currentAttributes = Get-AADConnectDirectoryExtensionAttribute if ($FullAttributeString) { $attributeValues = $FullAttributeString -split '\.' if ($attributeValues.Count -ne 4) { Write-Error "The attribute string did not have the correct format. Make sure it is like 'attributeName.group.String.True'" return } $Name = $attributeValues[0] $AssignedObjectClass = $attributeValues[1] $Type = $attributeValues[2] $IsEnabled = $attributeValues[3] } if ($currentAttributes | Where-Object { $_.Name -eq $Name -and $_.AssignedObjectClass -eq $AssignedObjectClass -and $_.Type -eq $Type -and $_.IsEnabled -eq $IsEnabled }) { Write-Error "The attribute '$Name' with the type '$Type' assigned to the class '$AssignedObjectClass' is already defined." return } if (($existingAttribute = $currentAttributes | Where-Object { $_.Name -eq $Name -and $_.Type -ne $Type }) -and -not $Force) { Write-Error "The attribute '$Name' is already defined with the type '$($existingAttribute.Type)'." return } else { $existingAttribute | Remove-AADConnectDirectoryExtensionAttribute } $settings = Get-ADSyncGlobalSettings $attributeParameter = $settings.Parameters | Where-Object Name -EQ Microsoft.OptionalFeature.DirectoryExtensionAttributes $currentAttributeList = $attributeParameter.Value -split ',' $newAttributeString = "$Name.$AssignedObjectClass.$Type.$IsEnabled" $currentAttributeList += $newAttributeString $attributeParameter.Value = $currentAttributeList -join ',' $settings.Parameters.AddOrReplace($attributeParameter) Set-ADSyncGlobalSettings -GlobalSettings $settings | Out-Null } } #EndRegion '.\Public\Add-AADConnectDirectoryExtensionAttribute.ps1' 167 #Region '.\Public\Convert-ObjectToHashtable.ps1' -1 <# .SYNOPSIS Converts a PowerShell object to a hashtable. .DESCRIPTION The Convert-ObjectToHashtable function converts any PowerShell object to a hashtable by extracting all properties and their values. This utility function is commonly used in DSC configurations and Azure AD Connect management scenarios where hashtable representations of objects are needed for parameter passing or configuration storage. The function filters out properties with null values to create a clean hashtable with only meaningful data. This is particularly useful when working with Azure AD Connect objects that may have many optional properties. This function works with both Windows PowerShell 5.1 and PowerShell 7. .PARAMETER Object Specifies the PowerShell object to convert to a hashtable. The object can be of any type that has properties accessible through the PSObject.Properties collection. .EXAMPLE $syncRule = Get-ADSyncRule -Name "In from AD - User Common" $hashtable = Convert-ObjectToHashtable -Object $syncRule Converts an Azure AD Connect synchronization rule object to a hashtable. .EXAMPLE Get-ADSyncRule | Select-Object -First 1 | Convert-ObjectToHashtable Retrieves a synchronization rule and converts it to a hashtable using pipeline input. .EXAMPLE $user = [PSCustomObject]@{ Name = "John Doe" Email = "john.doe@contoso.com" Department = $null Enabled = $true } $hashtable = Convert-ObjectToHashtable -Object $user # Results in: @{ Name = "John Doe"; Email = "john.doe@contoso.com"; Enabled = $true } Converts a custom object to a hashtable, excluding null properties. .EXAMPLE $config = @{ SyncRules = Get-ADSyncRule | ForEach-Object { Convert-ObjectToHashtable $_ } } Creates a configuration hashtable containing all synchronization rules as hashtables. .INPUTS Object. You can pipe any PowerShell object to Convert-ObjectToHashtable. .OUTPUTS Hashtable. Returns a hashtable containing all non-null properties and their values. .NOTES - This function works with both Windows PowerShell 5.1 and PowerShell 7 - Properties with null values are excluded from the resulting hashtable - Complex nested objects are included as-is (not recursively converted) - The function is optimized for performance and memory efficiency - Useful for DSC configurations and Azure AD Connect object manipulation .LINK https://docs.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-hashtable .COMPONENT AADConnectDsc .FUNCTIONALITY PowerShell Object Utilities #> function Convert-ObjectToHashtable { [OutputType([hashtable])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [object]$Object ) process { $hashtable = @{ } foreach ($property in $Object.PSObject.Properties.Where({ $null -ne $_.Value })) { $hashtable.Add($property.Name, $property.Value) } $hashtable } } #EndRegion '.\Public\Convert-ObjectToHashtable.ps1' 93 #Region '.\Public\Get-AADConnectDirectoryExtensionAttribute.ps1' -1 <# .SYNOPSIS Retrieves directory extension attributes from Azure AD Connect configuration. .DESCRIPTION The Get-AADConnectDirectoryExtensionAttribute function retrieves directory extension attributes that are currently configured in Azure AD Connect global settings. These attributes represent schema extensions that allow synchronization of custom attributes from on-premises Active Directory to Azure AD. The function can retrieve all directory extension attributes or filter by a specific attribute name. Each returned object contains the attribute name, data type, assigned object class, and enabled status. This function requires Windows PowerShell 5.1 and does not work with PowerShell 7. .PARAMETER Name Specifies the name of a specific directory extension attribute to retrieve. If not specified, all directory extension attributes are returned. Supports wildcard patterns. .EXAMPLE Get-AADConnectDirectoryExtensionAttribute Retrieves all directory extension attributes currently configured in Azure AD Connect. .EXAMPLE Get-AADConnectDirectoryExtensionAttribute -Name "employeeNumber" Retrieves the directory extension attribute named "employeeNumber" if it exists. .EXAMPLE Get-AADConnectDirectoryExtensionAttribute -Name "employee*" Retrieves all directory extension attributes with names starting with "employee". .EXAMPLE $attributes = Get-AADConnectDirectoryExtensionAttribute $attributes | Where-Object Type -eq "String" Retrieves all directory extension attributes and filters for those with String data type. .INPUTS None. You cannot pipe objects to Get-AADConnectDirectoryExtensionAttribute. .OUTPUTS PSCustomObject. Returns objects with the following properties: - Name: The attribute name - Type: The data type (String, Integer, Boolean, DateTime, etc.) - AssignedObjectClass: The object class (user, group, contact, device, etc.) - IsEnabled: Whether the attribute is enabled for synchronization .NOTES - This function requires Windows PowerShell 5.1 and does not work with PowerShell 7 - Requires Azure AD Connect to be installed and the ADSync module to be available - Returns an empty result if no directory extension attributes are configured - The returned objects can be used as input for other directory extension attribute functions .LINK https://docs.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-sync-feature-directory-extensions .COMPONENT AADConnectDsc .FUNCTIONALITY Azure AD Connect Directory Extension Attribute Management #> function Get-AADConnectDirectoryExtensionAttribute { param ( [Parameter()] [string]$Name ) $settings = Get-ADSyncGlobalSettings $attributeParameter = $settings.Parameters | Where-Object Name -EQ Microsoft.OptionalFeature.DirectoryExtensionAttributes $attributes = $attributeParameter.Value -split ',' if (-not $attributes) { return } if ($Name) { $attributes = $attributes | Where-Object { $_ -like "$Name.*" } if (-not $attributes) { Write-Error "The attribute '$Name' is not defined." return } } foreach ($attribute in $attributes) { $attribute = $attribute -split '\.' [pscustomobject]@{ Name = $attribute[0] Type = $attribute[2] AssignedObjectClass = $attribute[1] IsEnabled = $attribute[3] } } } #EndRegion '.\Public\Get-AADConnectDirectoryExtensionAttribute.ps1' 104 #Region '.\Public\Get-ADSyncRule.ps1' -1 <# .SYNOPSIS Retrieves Azure AD Connect synchronization rules with enhanced filtering capabilities. .DESCRIPTION The Get-ADSyncRule function provides a wrapper around the native ADSync\Get-ADSyncRule cmdlet, adding enhanced filtering capabilities by name and connector. This function supports multiple parameter sets for flexible rule retrieval and is designed to work with Windows PowerShell 5.1. This function is part of the AADConnectDsc module and requires an active Azure AD Connect installation with the ADSync PowerShell module available. .PARAMETER Name Specifies the name of the synchronization rule to retrieve. When used alone, it searches across all connectors. When used with ConnectorName, it searches within the specified connector. .PARAMETER Identifier Specifies the unique identifier (GUID) of the synchronization rule to retrieve. When specified, all other parameters are ignored. .PARAMETER ConnectorName Specifies the name of the connector to filter synchronization rules. Can be used alone to get all rules for a connector, or with Name for specific rule lookup. .EXAMPLE Get-ADSyncRule Retrieves all synchronization rules from Azure AD Connect. .EXAMPLE Get-ADSyncRule -Name "In from AD - User Common" Retrieves the synchronization rule with the specified name from any connector. .EXAMPLE Get-ADSyncRule -Identifier "12345678-1234-1234-1234-123456789012" Retrieves the synchronization rule with the specified GUID identifier. .EXAMPLE Get-ADSyncRule -ConnectorName "contoso.com" Retrieves all synchronization rules associated with the specified connector. .EXAMPLE Get-ADSyncRule -Name "In from AD - User Common" -ConnectorName "contoso.com" Retrieves the synchronization rule with the specified name from the specified connector. .INPUTS None. You cannot pipe objects to Get-ADSyncRule. .OUTPUTS Microsoft.IdentityManagement.PowerShell.ObjectModel.SynchronizationRule Returns synchronization rule objects that match the specified criteria. .NOTES - This function requires Windows PowerShell 5.1 and does not work with PowerShell 7 - Requires Azure AD Connect to be installed and the ADSync module to be available - The function provides enhanced error handling and parameter validation - Multiple parameter sets allow for flexible rule retrieval scenarios .LINK https://docs.microsoft.com/en-us/azure/active-directory/hybrid/reference-connect-sync-functions-reference .COMPONENT AADConnectDsc .FUNCTIONALITY Azure AD Connect Synchronization Rule Management #> function Get-ADSyncRule { [CmdletBinding(DefaultParameterSetName = 'ByName')] param ( [Parameter(ParameterSetName = 'ByName')] [Parameter(Mandatory = $true, ParameterSetName = 'ByNameAndConnector')] [string] $Name, [Parameter(ParameterSetName = 'ByIdentifier')] [guid] $Identifier, [Parameter(Mandatory = $true, ParameterSetName = 'ByNameAndConnector')] [Parameter(Mandatory = $true, ParameterSetName = 'ByConnector')] [string] $ConnectorName ) $connectors = Get-ADSyncConnector if ($PSCmdlet.ParameterSetName -eq 'ByIdentifier') { ADSync\Get-ADSyncRule -Identifier $Identifier } elseif ($PSCmdlet.ParameterSetName -eq 'ByName') { if ($Name) { ADSync\Get-ADSyncRule | Where-Object Name -EQ $Name } else { ADSync\Get-ADSyncRule } } elseif ($PSCmdlet.ParameterSetName -eq 'ByConnector') { $connector = $connectors | Where-Object Name-eq $ConnectorName ADSync\Get-ADSyncRule | Where-Object Connector -EQ $connector.Identifier } elseif ($PSCmdlet.ParameterSetName -eq 'ByNameAndConnector') { $connector = $connectors | Where-Object Name -EQ $ConnectorName if ($null -eq $connector) { Write-Error "The connector '$ConnectorName' does not exist" return } ADSync\Get-ADSyncRule | Where-Object { $_.Name -eq $Name -and $_.Connector -eq $connector.Identifier } } else { ADSync\Get-ADSyncRule } } #EndRegion '.\Public\Get-ADSyncRule.ps1' 128 #Region '.\Public\Remove-AADConnectDirectoryExtensionAttribute.ps1' -1 <# .SYNOPSIS Removes a directory extension attribute from Azure AD Connect configuration. .DESCRIPTION The Remove-AADConnectDirectoryExtensionAttribute function removes a directory extension attribute from the Azure AD Connect global settings. This function allows you to clean up unused or incorrectly configured directory extension attributes from the synchronization configuration. The function supports two parameter sets: specifying individual properties or providing a complete attribute string. It includes validation to ensure the attribute exists before removal. WARNING: Removing a directory extension attribute that is actively used in synchronization rules may cause synchronization errors. Ensure the attribute is not referenced before removal. This function requires Windows PowerShell 5.1 and does not work with PowerShell 7. .PARAMETER Name Specifies the name of the directory extension attribute to remove. Must match exactly with an existing attribute name. .PARAMETER Type Specifies the data type of the directory extension attribute to remove. Must match exactly with the existing attribute's type (String, Integer, Boolean, DateTime, etc.). .PARAMETER AssignedObjectClass Specifies the object class of the directory extension attribute to remove. Must match exactly with the existing attribute's object class (user, group, contact, device, etc.). .PARAMETER FullAttributeString Specifies a complete attribute definition string in the format: "attributeName.objectClass.dataType.enabledStatus" For example: "employeeNumber.user.String.True" .EXAMPLE Remove-AADConnectDirectoryExtensionAttribute -Name "employeeNumber" -Type "String" -AssignedObjectClass "user" Removes the employee number directory extension attribute for user objects. .EXAMPLE Remove-AADConnectDirectoryExtensionAttribute -FullAttributeString "departmentCode.user.String.True" Removes the department code directory extension attribute using the full attribute string format. .EXAMPLE Get-AADConnectDirectoryExtensionAttribute -Name "obsolete*" | Remove-AADConnectDirectoryExtensionAttribute Removes all directory extension attributes with names starting with "obsolete". .EXAMPLE $attribute = Get-AADConnectDirectoryExtensionAttribute -Name "tempAttribute" if ($attribute) { Remove-AADConnectDirectoryExtensionAttribute -Name $attribute.Name -Type $attribute.Type -AssignedObjectClass $attribute.AssignedObjectClass } Safely removes a directory extension attribute after verifying it exists. .INPUTS PSCustomObject. You can pipe directory extension attribute objects from Get-AADConnectDirectoryExtensionAttribute. .OUTPUTS None. This function does not return objects but modifies Azure AD Connect global settings. .NOTES - This function requires Windows PowerShell 5.1 and does not work with PowerShell 7 - Requires Azure AD Connect to be installed and the ADSync module to be available - Changes take effect immediately but may require synchronization cycle restart - Verify that the attribute is not used in synchronization rules before removal - Use Get-AADConnectDirectoryExtensionAttribute to verify the attribute was removed successfully - Removed attributes cannot be recovered; back up configuration before making changes .LINK https://docs.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-sync-feature-directory-extensions .COMPONENT AADConnectDsc .FUNCTIONALITY Azure AD Connect Directory Extension Attribute Management #> function Remove-AADConnectDirectoryExtensionAttribute { [CmdletBinding(DefaultParameterSetName = 'ByProperties')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByProperties')] [string]$Name, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByProperties')] [string]$Type, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByProperties')] [string]$AssignedObjectClass, [Parameter(Mandatory = $true, ParameterSetName = 'SingleObject')] $FullAttributeString ) process { $currentAttributes = Get-AADConnectDirectoryExtensionAttribute if ($FullAttributeString) { $attributeValues = $FullAttributeString -split '\.' if ($attributeValues.Count -ne 4) { Write-Error "The attribute string did not have the correct format. Make sure it is like 'attributeName.group.String.True'" return } $Name = $attributeValues[0] $AssignedObjectClass = $attributeValues[1] $Type = $attributeValues[2] $IsEnabled = $attributeValues[3] } if (-not ($existingAttribute = $currentAttributes | Where-Object { $_.Name -eq $Name -and $_.AssignedObjectClass -eq $AssignedObjectClass -and $_.Type -eq $Type })) { Write-Error "The attribute '$Name' with the type '$Type' assigned to the class '$AssignedObjectClass' is not defined." return } $settings = Get-ADSyncGlobalSettings $attributeParameter = $settings.Parameters | Where-Object Name -EQ Microsoft.OptionalFeature.DirectoryExtensionAttributes $currentAttributeList = $attributeParameter.Value -split ',' $attributeStringToRemove = "$($existingAttribute.Name).$($existingAttribute.AssignedObjectClass).$($existingAttribute.Type).$($existingAttribute.IsEnabled)" $currentAttributeList = $currentAttributeList -ne $attributeStringToRemove $attributeParameter.Value = $currentAttributeList -join ',' $settings.Parameters.AddOrReplace($attributeParameter) Set-ADSyncGlobalSettings -GlobalSettings $settings | Out-Null } } #EndRegion '.\Public\Remove-AADConnectDirectoryExtensionAttribute.ps1' 139 |