AADSyncSettings.ps1
# This module contains functions to extract and update AADConnect sync credentials # May 15th 2019 # Jul 26th 2024: Refactored to use service running as ADSync user function Get-SyncCredentials { <# .SYNOPSIS Gets Azure AD Connect synchronization credentials .Description Extracts Azure Active Directory Connect crecentials from WID configuration database. MUST be run on AADConnect server as local administrator .Example Get-AADIntSyncCredentials Name Value ---- ----- AADUser Sync_SRV01_4bc4a34e95fa@company.onmicrosoft.com AADUserPassword $.1%(lxZ&/kNZz[r ADDomain1 company.com ADUser1 MSOL_4bc4a34e95fa ADUserPassword1 Q9@p(poz{#:kF_G)(s/Iy@8c*9(t;... ADDomain2 business.net ADUser2 MSOL_4bc4a34e95fa ADUserPassword2 cE/Pj+4/MR6hW)2L_4P=H^hiq)pZhMb... .Example PS C:\>$synccredentials = Get-AADIntSyncCredentials -AsCredentials PS C:\>Get-AADIntAccessTokenForAADGraph -Credentials $synccredentials[0] -SaveToCache Tenant User Resource Client ------ ---- -------- ------ a5427106-ed71-4185-9481-221e2ebdfc6c Sync_SRV01_4bc4a34e95fa@company.onmicrosoft.com https://graph.windows.net 1b730954-1685-4b74... #> [cmdletbinding()] Param( [Parameter(Mandatory=$false)] [switch]$AsCredentials, [Parameter(Mandatory=$false)] [switch]$AsJson ) Process { $serviceWMI = Get-WmiObject Win32_Service -Filter "Name='ADSync'" -ErrorAction SilentlyContinue if(!$serviceWMI) { Throw "Service ADSync not found on this computer" } $ADSyncUser = $serviceWMI.StartName # Run as ADSync if((Get-CurrentUser) -ne $ADSyncUser) { # Check whether we are running in elevated session Test-LocalAdministrator -Throw | Out-Null Write-Verbose "Elevating to $ADSyncUser" $ADSyncCredentials = Get-LSASecrets -Account $ADSyncUser $cmdToRun = "Set-Location '$PSScriptRoot';. '.\CommonUtils.ps1';. '.\AADSyncSettings.ps1'; Get-SyncCredentials -AsJson" try { $credJson = Invoke-ScriptAs -Command $cmdToRun -Credentials $ADSyncCredentials.Credentials Write-Verbose "Invoke-ScriptAs response: $credJson" $retVal = ConvertFrom-Json -InputObject $credJson } catch { throw "Unable to get sync credentials as $ADSyncUser" } } else { # Add the encryption reference (should always be there) $ADSyncLocation = (Get-ItemProperty -Path "HKLM:SOFTWARE\Microsoft\AD Sync").Location Add-Type -path "$ADSyncLocation\Bin\mcrypt.dll" # Read the encrypt/decrypt key settings $SQLclient = new-object System.Data.SqlClient.SqlConnection -ArgumentList (Get-AADConfigDbConnection) $SQLclient.Open() $SQLcmd = $SQLclient.CreateCommand() $SQLcmd.CommandText = "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration" $SQLreader = $SQLcmd.ExecuteReader() $SQLreader.Read() | Out-Null $key_id = $SQLreader.GetInt32(0) $instance_id = $SQLreader.GetGuid(1) $entropy = $SQLreader.GetGuid(2) $SQLreader.Close() # Read the AD configuration data $ADConfigs=@() $SQLcmd = $SQLclient.CreateCommand() $SQLcmd.CommandText = "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE ma_type = 'AD'" $SQLreader = $SQLcmd.ExecuteReader() while($SQLreader.Read()) { $ADConfig = $SQLreader.GetString(0) $ADCryptedConfig = $SQLreader.GetString(1) $ADConfigs += New-Object -TypeName psobject -Property @{"ADConfig" = $ADConfig; "ADCryptedConfig" = $ADCryptedConfig} } $SQLreader.Close() # Read the AAD configuration data $SQLcmd = $SQLclient.CreateCommand() $SQLcmd.CommandText = "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE subtype = 'Windows Azure Active Directory (Microsoft)'" $SQLreader = $SQLcmd.ExecuteReader() $SQLreader.Read() | Out-Null $AADConfig = $SQLreader.GetString(0) $AADCryptedConfig = $SQLreader.GetString(1) $SQLreader.Close() $SQLclient.Close() # Extract the data $attributes=[ordered]@{} $attributes["AADUser"]=([xml]$AADConfig).MAConfig.'parameter-values'.parameter[0].'#text' $attributes["AADUserPassword"]="" try { # Decrypt config data $KeyMgr = New-Object -TypeName Microsoft.DirectoryServices.MetadirectoryServices.Cryptography.KeyManager $KeyMgr.LoadKeySet($entropy, $instance_id, $key_id) #$key = $null #$KeyMgr.GetActiveCredentialKey([ref]$key) $key2 = $null $KeyMgr.GetKey(1, [ref]$key2) # Extract the encrypted data $n=1 foreach($ADConfig in $ADConfigs) { $ADDecryptedConfig = $null $key2.DecryptBase64ToString($ADConfig.ADCryptedConfig, [ref]$ADDecryptedConfig) $attributes["ADDomain$n" ]=([xml]$ADConfig.ADConfig).'adma-configuration'.'forest-login-domain' $attributes["ADUser$n" ]=([xml]$ADConfig.ADConfig).'adma-configuration'.'forest-login-user' $attributes["ADUserPassword$n"]=([xml]$ADDecryptedConfig).'encrypted-attributes'.attribute.'#text' $n++ } $AADDecryptedConfig = $null $key2.DecryptBase64ToString($AADCryptedConfig, [ref]$AADDecryptedConfig) $attributes["AADUserPassword"]=([xml]$AADDecryptedConfig).'encrypted-attributes'.attribute | Where name -eq "Password" | Select -ExpandProperty "#text" $retVal = [PSCustomObject]$attributes } catch { Write-Error "Could not load key set!" } } # Create credentials objects if requested if($AsJson) { return ($retVal | ConvertTo-Json -Compress) } if($AsCredentials) { $credentials = @() # There is only one AAD credentials $credentials += New-Object System.Management.Automation.PSCredential($retVal.AADUser, (ConvertTo-SecureString $retVal.AADUserPassword -AsPlainText -Force)) # Loop through the on-prem AD credentials. Shouldn't be more than 100 :) for($n = 1 ; $n -lt 100 ; $n++) { if(![string]::IsNullOrEmpty($retVal."ADUser$n")) { $userName = "$($retVal."ADDomain$n")\$($retVal."ADUser$n")" $credentials += New-Object System.Management.Automation.PSCredential($userName, (ConvertTo-SecureString $retVal."ADUserPassword$n" -AsPlainText -Force)) } else { # No more on-prem AD credentials break } } return @($credentials) } else { return $retVal } } } # May 16th 2019 # Jul 27th 2024: Updated to use reflection function Update-SyncCredentials { <# .SYNOPSIS Updates Entra ID Connect synchronization credentials .Description Updates Entra ID Connect user's password to Entra ID and configuration database. MUST be run on AADConnect server as local administrator with Global Admin credentials to Entra ID .Example Update-AADIntSyncCredentials Password successfully updated to Entra ID and configuration sync client Remember to restart the sync service: Restart-Service ADSync .Example Update-AADIntSyncCredentials -RestartADSyncService Password successfully updated to Entra ID and configuration sync client WARNING: Waiting for service 'Microsoft Azure AD Sync (ADSync)' to stop... WARNING: Waiting for service 'Microsoft Azure AD Sync (ADSync)' to start... #> [cmdletbinding()] Param( [Parameter(Mandatory=$False)] [String]$AccessToken, [Switch]$RestartADSyncService, [Parameter(Mandatory=$False)] [pscredential]$Credentials ) Process { # The service must be running if((Get-Service ADSync).Status -ne "Running") { Throw "Synchronization Service is not running." } # Get the Entra ID connector $connector = Get-ADSyncConnector -Identifier "b891884f-051e-4a83-95af-2544101c9083" # Get the connector connectivity properties $parameters = $connector.GetType().GetField("connectivityParameters",[System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Public -bor [System.Reflection.BindingFlags]::Static).GetValue($connector) $userProperty = $parameters | Where-Object Name -eq "UserName" $pwdProperty = $parameters | Where-Object Name -eq "Password" # Use provided credentials if($Credentials) { $SyncUser = $Credentials.UserName $NewPassword = $Credentials.GetNetworkCredential().Password } else { # Get from cache if not provided $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net" if([String]::IsNullOrEmpty($AccessToken)) { Write-Error "No AccessToken provided!" return } # Admin user $AdminUser = (Read-Accesstoken -AccessToken $at).upn $SyncUser = $userProperty.Value Write-Verbose "Updating password for $SyncUser as $AdminUser" # Reset the account password in Entra ID and use the returned password $NewPassword = (Reset-ServiceAccount -AccessToken $AccessToken -ServiceAccount $SyncUser).Password } # Set the new password $pwdProperty.Value = $NewPassword # Update the password to configuration $result = Add-ADSyncConnector -Connector $connector Write-Host "Password successfully updated to Entra ID and sync client" # Restart the ADSync service if requested if($RestartADSyncService) { Restart-Service ADSync } else { Write-Host "Remember to restart the sync service: Restart-Service ADSync" -ForegroundColor Yellow } } } # May 17th 2019 function Set-ADSyncAccountPassword { <# .SYNOPSIS Sets the password of ADSync service account .Description Sets the password of ADSync service account to AD and WID configuration database. MUST be run on AADConnect server as domain administrator. .Example Set-AADIntADSyncAccountPassword -NewPassword 'Pa$$w0rd' Password successfully updated to AD and configuration database! Remember to restart the sync service: Restart-Service ADSync Name Value ---- ----- ADDomain company.com ADUser MSOL_4bc4a34e95fa ADUserPassword Pa$$w0rd AADUser Sync_SRV01_4bc4a34e95fa@company.onmicrosoft.com AADUserPassword $.1%(lxZ&/kNZz[r .Example Set-AADIntADSyncAccountPassword -NewPassword 'Pa$$w0rd' -RestartADSyncService Password successfully updated to AD and configuration database! Name Value ---- ----- ADDomain company.com ADUser MSOL_4bc4a34e95fa ADUserPassword Pa$$w0rd AADUser Sync_SRV01_4bc4a34e95fa@company.onmicrosoft.com AADUserPassword $.1%(lxZ&/kNZz[r WARNING: Waiting for service 'Microsoft Azure AD Sync (ADSync)' to stop... WARNING: Waiting for service 'Microsoft Azure AD Sync (ADSync)' to start... #> [cmdletbinding()] Param( [Parameter(Mandatory=$True)] [String]$NewPassword, [Switch]$RestartADSyncService, [Parameter(Mandatory=$false)] [switch]$force ) Process { # Do the checks if((Check-Server -force $force) -eq $false) { return } # Add the encryption dll reference Add-Type -path "$(Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\AD Sync" -Name "Location")\Bin\mcrypt.dll" # Get the current configuration $SyncCreds = Get-SyncCredentials -force $SyncUser = $SyncCreds.ADUser Write-Verbose "Updating password for $SyncUser" # Reset the account password in AD try { Set-ADAccountPassword -Identity $SyncUser -Reset -NewPassword (ConvertTo-SecureString -AsPlainText $NewPassword -Force) } catch { # There might be complexity etc. requirements throw $_ return } # Escaping password for xml $NewPassword = [System.Security.SecurityElement]::Escape($NewPassword) # Create a new config $ADDecryptedConfig=@" <encrypted-attributes> <attribute name="Password">$NewPassword</attribute> </encrypted-attributes> "@ # Read the encrypt/decrypt key settings $SQLclient = new-object System.Data.SqlClient.SqlConnection -ArgumentList (Get-AADConfigDbConnection) $SQLclient.Open() $SQLcmd = $SQLclient.CreateCommand() $SQLcmd.CommandText = "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration" $SQLreader = $SQLcmd.ExecuteReader() $SQLreader.Read() | Out-Null $key_id = $SQLreader.GetInt32(0) $instance_id = $SQLreader.GetGuid(1) $entropy = $SQLreader.GetGuid(2) $SQLreader.Close() # Load keys $KeyMgr = New-Object -TypeName Microsoft.DirectoryServices.MetadirectoryServices.Cryptography.KeyManager $KeyMgr.LoadKeySet($entropy, $instance_id, $key_id) $key = $null $KeyMgr.GetActiveCredentialKey([ref]$key) $key2 = $null $KeyMgr.GetKey(1, [ref]$key2) # Encrypt $ADCryptedConfig = $null $key2.EncryptStringToBase64($ADDecryptedConfig,[ref]$ADCryptedConfig) # Write the updated AA password $SQLcmd = $SQLclient.CreateCommand() $SQLcmd.CommandText = "UPDATE mms_management_agent SET encrypted_configuration=@pwd WHERE ma_type = 'AD'" $SQLcmd.Parameters.AddWithValue("@pwd",$ADCryptedConfig) | Out-Null $UpdatedRows = $SQLcmd.ExecuteNonQuery() $SQLclient.Close() if($UpdatedRows -ne 1) { Write-Error "Updated $UpdatedRows while should update 1. Could be error" return } Write-Host "Password successfully updated to AD and configuration database!" # Return Get-SyncCredentials -force # Restart the ADSync service if requested if($RestartADSyncService) { Restart-Service ADSync } else { Write-Host "Remember to restart the sync service: Restart-Service ADSync" -ForegroundColor Yellow } } } # Gets the db connection string from the registry # May 11th 2020 function Get-AADConfigDbConnection { [cmdletbinding()] Param() Begin { # Create the connection string for the configuration database $parametersPath = "HKLM:\SYSTEM\CurrentControlSet\Services\ADSync\Parameters" $dBServer = (Get-ItemProperty -Path $parametersPath).Server $dBName = (Get-ItemProperty -Path $parametersPath).DBName $dBInstance = (Get-ItemProperty -Path $parametersPath).SQLInstance $connectionString = "Data Source=$dbServer\$dBInstance;Initial Catalog=$dBName" # If not using local WID, use ADSync account credentials if($dBServer -ne "(localdb)") { $connectionString += ";Integrated Security=true" } } Process { Write-Verbose "ConnectionString=$connectionString" return $connectionString } } |