Modules/AzureDevOpsDsc.Common/Api/Functions/Private/Helper/Protect-AzDoExportedSecretProperty.ps1

<#
.SYNOPSIS
Redacts secret-bearing properties from a resource's exported property set.
 
.DESCRIPTION
Protect-AzDoExportedSecretProperty is the single place that guarantees a secret (a password, a
token, a connection string) never leaves DSC v3 Export() in a returned configuration. A resource
that carries a secret DscProperty overrides GetDscResourceSecretPropertyNames() to name it; this
function is then called, once, by the shared AzDevOpsDscResourceBase.ExportDscResourceInstances()
helper before the exported hashtable is ever turned into a resource instance.
 
For every name in -SecretPropertyNames that is present in -Properties, the value is replaced with
a fixed placeholder ('<REDACTED-BY-EXPORT>') and a warning is written naming both the resource and
the property, so redaction is never silent. Properties not named as secret pass through unchanged.
The input hashtable is not mutated; a copy is returned.
 
.PARAMETER ResourceName
The DSC resource name (as GetResourceName()/ResourceName report it), used only for the warning
message.
 
.PARAMETER SecretPropertyNames
The property names the resource's GetDscResourceSecretPropertyNames() declared secret. An empty
or $null array is a no-op - the function still returns a copy of -Properties unchanged.
 
.PARAMETER Properties
The hashtable of exported property values (as returned by an Export-<ResourceName> function)
to redact.
 
.OUTPUTS
System.Collections.Hashtable
A copy of -Properties with every secret property's value replaced by the placeholder.
 
.EXAMPLE
Protect-AzDoExportedSecretProperty -ResourceName 'AzDoServiceEndpoint' -SecretPropertyNames @('ApiToken') -Properties @{ Name = 'MyEndpoint'; ApiToken = 's3cr3t' }
 
Returns @{ Name = 'MyEndpoint'; ApiToken = '<REDACTED-BY-EXPORT>' } and writes a warning naming
'AzDoServiceEndpoint' and 'ApiToken'.
#>

function Protect-AzDoExportedSecretProperty
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]$ResourceName,

        [Parameter()]
        [AllowNull()]
        [AllowEmptyCollection()]
        [System.String[]]$SecretPropertyNames,

        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]$Properties
    )

    # Fixed placeholder every secret-bearing exported property is replaced with. Kept as a single
    # constant so every caller (and every test asserting on it) agrees on the exact value.
    $script:AzDoExportedSecretPlaceholder = '<REDACTED-BY-EXPORT>'

    $protectedProperties = @{} + $Properties

    if ($null -eq $SecretPropertyNames -or $SecretPropertyNames.Count -eq 0)
    {
        return $protectedProperties
    }

    foreach ($secretPropertyName in $SecretPropertyNames)
    {
        if ($protectedProperties.ContainsKey($secretPropertyName))
        {
            $protectedProperties[$secretPropertyName] = $script:AzDoExportedSecretPlaceholder
            Write-Warning "[Protect-AzDoExportedSecretProperty] Redacted secret property '$secretPropertyName' of resource '$ResourceName' in exported output. Set its real value manually after import."
        }
    }

    return $protectedProperties
}