public/Compare-SfStateAndCountrySettings.ps1
|
function Compare-SfStateAndCountrySettings { <# .SYNOPSIS Compares the TO-BE state/country picklist entries from the Data Dictionary against the AS-IS entries in the org's Address Settings and returns the resulting delta as an action list. .DESCRIPTION TO-BE is read from MetadataModel.Tables._StateAndCountryPicklists, as produced by Import-SfMetadataModel. AS-IS is read from the Address Settings metadata XML at SourcePath, converted via ConvertFrom-SfCountriesAndStatesXml. For matching, '.' and an empty string are both treated as "no state code" (i.e. a country row), consistent with how '.' is treated elsewhere in the Data Dictionary. TO-BE vs. AS-IS differences are classified by which mechanism can fix them: - A _Label difference requires the Setup UI (Create.../Update...ViaSetup), because _Label is the only property that cannot be set via a regular Metadata API deployment and must go through Add-SfCountryPicklistValue / Add-SfStatePicklistValue / Update-SfCountryPicklistValue / Update-SfStatePicklistValue instead. - A difference in _IntegrationValue, _IsStandard, _IsDefaultCountry or _Status is reported as Deploy, because these properties can be set via a regular Metadata API deployment of Address.settings-meta.xml. Deploy is not split into country/state variants, because a deployment is typically applied to the whole file at once, not per entry. - An entry with no difference at all is reported as Equal. Equal rows are always part of the output, regardless of -Verbose. The result is sorted by _CountryCode, _StateCode and is intended to be piped into Set-SfStateAndCountryPicklistValue. .OUTPUTS [PSCustomObject] One object per TO-BE entry, plus one per AS-IS-only entry (AddToDataDictionary). Properties: Action, _CountryCode, _StateCode, _IsStandard, _IsDefaultCountry, _Status, _IntegrationValue and _Label. Action is one of: - CreateCountryViaSetup - country exists in TO-BE but not in AS-IS - CreateStateViaSetup - state exists in TO-BE but not in AS-IS - UpdateCountryViaSetup - country exists in both, but _Label differs - UpdateStateViaSetup - state exists in both, but _Label differs - Deploy - entry exists in both, _Label matches, but _IntegrationValue, _IsStandard, _IsDefaultCountry or _Status differs - Equal - entry exists in both and every compared property matches - AddToDataDictionary - entry exists in AS-IS but not in TO-BE (_CountryCode/_StateCode/ _IntegrationValue/_Label are taken from AS-IS in this case) .EXAMPLE Compare-SfStateAndCountrySettings -MetadataModel $MetadataModel | Set-SfStateAndCountryPicklistValue -WebSession $WebSession .LINK https://codeberg.org/uwe-braig/sf-metadata-toolkit #> [CmdletBinding()] param ( # TO-BE: metadata model as returned by Import-SfMetadataModel. [Parameter(Mandatory, Position = 0)] [PSCustomObject] $MetadataModel, # AS-IS: path to the Address Settings metadata XML. [Parameter(Position = 1)] [ValidateScript({ Test-Path $_ -PathType Leaf })] [string] $SourcePath = (Join-Path -Path 'force-app' -ChildPath 'main' -AdditionalChildPath 'default', 'settings', 'Address.settings-meta.xml') ) $ScriptName = 'Compare-SfStateAndCountrySettings' $ErrorActionPreference = 'Stop' [bool] $IsDebug = $DebugPreference -ne 'SilentlyContinue' [bool] $IsVerbose = $VerbosePreference -ne 'SilentlyContinue' if ($null -eq $MetadataModel.PSObject.Properties['Tables']) { throw "$ScriptName|MetadataModel has no 'Tables' property - was it created by Import-SfMetadataModel?" } if (-not $MetadataModel.Tables.ContainsKey('_StateAndCountryPicklists')) { throw "$ScriptName|MetadataModel.Tables has no '_StateAndCountryPicklists' - check the Data Dictionary." } # Nested helpers - reduced overhead applies (coding conventions, section 11). # '.' and '' both mean "no state code" (i.e. a country row) - treat them as equal for matching. function GetNormalizedStateCode { param ([string] $StateCode) if ($StateCode -eq '.') { return '' } return $StateCode } function GetKey { param ([string] $CountryCode, [string] $StateCode) return "$CountryCode|$(GetNormalizedStateCode $StateCode)" } function ConvertToActionRow { param ([string] $Action, $Row) return [PSCustomObject]@{ Action = $Action _CountryCode = $Row._CountryCode _StateCode = $Row._StateCode _IsStandard = $Row._IsStandard _IsDefaultCountry = $Row._IsDefaultCountry _Status = $Row._Status _IntegrationValue = $Row._IntegrationValue _Label = $Row._Label } } # --- AS-IS: read from the Address Settings metadata XML Write-Verbose "$ScriptName|Reading AS-IS from '$SourcePath'" $Xml = Get-SfMetadataXmlFile -Path $SourcePath [array] $AsIsRows = @( ConvertFrom-SfCountriesAndStatesXml -Countries $Xml.AddressSettings.countriesAndStates.countries ) [hashtable] $AsIsByKey = @{} foreach ($Row in $AsIsRows) { $AsIsByKey[(GetKey $Row._CountryCode $Row._StateCode)] = $Row } # --- TO-BE: read from the Data Dictionary Write-Verbose "$ScriptName|Reading TO-BE from MetadataModel.Tables._StateAndCountryPicklists" [array] $ToBeRows = @($MetadataModel.Tables._StateAndCountryPicklists) [hashtable] $ToBeByKey = @{} foreach ($Row in $ToBeRows) { $ToBeByKey[(GetKey $Row._CountryCode $Row._StateCode)] = $Row } [array] $Results = @( $( # --- TO-BE vs. AS-IS: Setup-UI creates/updates, Deploy, Equal foreach ($Row in $ToBeRows) { [bool] $IsCountry = (GetNormalizedStateCode $Row._StateCode) -eq '' $AsIsRow = $AsIsByKey[(GetKey $Row._CountryCode $Row._StateCode)] if ($null -eq $AsIsRow) { [string] $Action = $IsCountry ? 'CreateCountryViaSetup' : 'CreateStateViaSetup' Write-Output (ConvertToActionRow $Action $Row) continue } if ($AsIsRow._Label -ne $Row._Label) { [string] $Action = $IsCountry ? 'UpdateCountryViaSetup' : 'UpdateStateViaSetup' Write-Output (ConvertToActionRow $Action $Row) continue } if (($AsIsRow._IntegrationValue -ne $Row._IntegrationValue) -or ($AsIsRow._IsStandard -ne $Row._IsStandard) -or ($AsIsRow._IsDefaultCountry -ne $Row._IsDefaultCountry) -or ($AsIsRow._Status -ne $Row._Status) ) { Write-Output (ConvertToActionRow 'Deploy' $Row) continue } Write-Output (ConvertToActionRow 'Equal' $Row) } # --- AS-IS vs. TO-BE: entries present in the org but missing from the Data Dictionary foreach ($Row in $AsIsRows) { if ($null -eq $ToBeByKey[(GetKey $Row._CountryCode $Row._StateCode)]) { Write-Output (ConvertToActionRow 'AddToDataDictionary' $Row) } } ) | Sort-Object -Property '_CountryCode', '_StateCode' ) [string] $Summary = ($Results | Group-Object -Property 'Action' | ForEach-Object { "$($_.Name)=$($_.Count)" }) -join ', ' Write-Host "$ScriptName|Comparison complete|$Summary" return @($Results) } |