Public/ColorProfiles.ps1

function Export-ColorProfile {
    <#
    .SYNOPSIS
    Saves style profiles and registered color names to a file.
 
    .DESCRIPTION
    Export-ColorProfile writes the style profiles in [PSColorStyle]::Profiles, or those named,
    and every color name Register-ColorName added, to a JSON file that Import-ColorProfile reads
    back. The file also names the default style when it is one of the profiles saved.
 
    A profile holds the properties that differ from a new PSColorStyle's, and its colors as
    given. The file is UTF-8 without a byte order mark, with LF line ends, and the same in Windows
    PowerShell 5.1 and PowerShell 7. A file at the path is replaced.
 
    .PARAMETER Path
    The file to write.
 
    .PARAMETER Name
    The profiles to save. Wildcards are allowed.
 
    Default: every profile
 
    .EXAMPLE
    Export-ColorProfile -Path ~/colors.json
 
    .EXAMPLE
    Export-ColorProfile -Path ./build-colors.json -Name Build*
 
    Saves the profiles whose names start with Build.
 
    .INPUTS
    None
 
    .OUTPUTS
    None
 
    .NOTES
    Author: Mark Newton
    License: MIT
    Requires: PowerShell 5.1 or later
 
    .LINK
    https://github.com/MarkusMcNugen/PSWriteColorEX
 
    .LINK
    Import-ColorProfile
    #>

    [CmdletBinding()]
    [Alias('Export-ColourProfile')]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string] $Path,

        [SupportsWildcards()]
        [string[]] $Name = '*'
    )

    # The profiles named, in the order of their names in upper case, ordinal, the same everywhere
    $profileNames = [string[]]@([PSColorStyle]::Profiles.Keys | Where-Object {
        $key = $_
        @($Name | Where-Object { $key -like $_ }).Count -gt 0
    })
    $keys = [string[]]@(foreach ($profileName in $profileNames) { $profileName.ToUpperInvariant() })
    [Array]::Sort($keys, $profileNames, [System.StringComparer]::Ordinal)
    $blank = [PSColorStyle]::new()

    $document = [ordered]@{}
    $colors = [ordered]@{}
    foreach ($colorName in $script:CustomColors.Keys) {
        $rgb = $script:CustomColors[$colorName][4]
        $colors[$colorName] = '#{0:X2}{1:X2}{2:X2}' -f $rgb[0], $rgb[1], $rgb[2]
    }
    $document['Colors'] = $colors

    $default = $null
    $profiles = [System.Collections.Generic.List[object]]::new()
    foreach ($profileName in $profileNames) {
        $style = [PSColorStyle]::Profiles[$profileName]
        if ([object]::ReferenceEquals($style, [PSColorStyle]::Default)) {
            $default = $style.Name
        }
        $entry = [ordered]@{ Name = $style.Name }
        foreach ($property in $script:ColorProfileProperties) {
            $value = $style.$property
            if ($property -eq 'ForegroundColor' -or $property -eq 'BackgroundColor') {
                $entry[$property] = $value
                continue
            }
            $isDefault = if ($value -is [array]) { $value.Count -eq 0 } else { $value -eq $blank.$property }
            if ($null -ne $value -and -not $isDefault) {
                $entry[$property] = if ($value -is [char]) { [string]$value } else { $value }
            }
        }
        $profiles.Add($entry)
    }
    if ($null -ne $default) {
        $document['Default'] = $default
    }
    $document['Profiles'] = $profiles

    $file = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
    $text = (ConvertTo-ColorJson -Value $document) + "`n"
    [System.IO.File]::WriteAllText($file, $text, [System.Text.UTF8Encoding]::new($false))
}

function Import-ColorProfile {
    <#
    .SYNOPSIS
    Reads style profiles and color names from a file Export-ColorProfile wrote.
 
    .DESCRIPTION
    Import-ColorProfile registers the file's color names, replacing names in use, then adds its
    profiles to [PSColorStyle]::Profiles, replacing profiles of the same name, and makes the
    style the file names the default style.
 
    .PARAMETER Path
    The file to read.
 
    .PARAMETER PassThru
    Writes the profiles read to the pipeline.
 
    .EXAMPLE
    Import-ColorProfile -Path ~/colors.json
    Write-ColorEX -Text 'Deploying' -StyleProfile ([PSColorStyle]::Profiles['Build'])
 
    .EXAMPLE
    # In a PowerShell profile script, so the styles are there in every session
    Import-ColorProfile -Path (Join-Path (Split-Path $PROFILE) 'colors.json')
 
    .INPUTS
    None
 
    .OUTPUTS
    None, or PSColorStyle with -PassThru
 
    .NOTES
    Author: Mark Newton
    License: MIT
    Requires: PowerShell 5.1 or later
 
    .LINK
    https://github.com/MarkusMcNugen/PSWriteColorEX
 
    .LINK
    Export-ColorProfile
    #>

    [CmdletBinding()]
    [Alias('Import-ColourProfile')]
    [OutputType([PSColorStyle])]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string] $Path,

        [switch] $PassThru
    )

    $file = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
    try {
        $document = [System.IO.File]::ReadAllText($file) | ConvertFrom-Json -ErrorAction Stop
    } catch {
        $PSCmdlet.ThrowTerminatingError([System.Management.Automation.ErrorRecord]::new(
            [System.IO.InvalidDataException]::new("Cannot read the profiles in '$Path'. $($_.Exception.Message)"),
            'InvalidProfileFile', [System.Management.Automation.ErrorCategory]::InvalidData, $Path))
    }

    if ($document.Colors) {
        foreach ($property in $document.Colors.PSObject.Properties) {
            Register-ColorName -Name $property.Name -Color (ConvertFrom-ColorJsonValue -Value $property.Value) -Force
        }
    }

    foreach ($entry in @($document.Profiles)) {
        if ($null -eq $entry -or [string]::IsNullOrEmpty($entry.Name)) {
            continue
        }
        $style = [PSColorStyle]::new([string]$entry.Name, $null, $null)
        foreach ($property in $script:ColorProfileProperties) {
            $member = $entry.PSObject.Properties[$property]
            if ($null -ne $member) {
                $style.$property = ConvertFrom-ColorJsonValue -Value $member.Value
            }
        }
        $style.AddToProfiles()
        if ($document.Default -and $document.Default -eq $style.Name) {
            $style.SetAsDefault()
        }
        if ($PassThru) {
            $style
        }
    }
}

function Remove-ColorProfile {
    <#
    .SYNOPSIS
    Removes style profiles from [PSColorStyle]::Profiles.
 
    .DESCRIPTION
    A removed built-in profile, such as Error, leaves its helper, Write-ColorError, with the
    colors and styles it starts with. A name with no profile gives an error.
 
    .PARAMETER Name
    The profiles to remove.
 
    .EXAMPLE
    Remove-ColorProfile -Name Build
 
    .INPUTS
    System.String[]
    Names piped in are removed.
 
    .OUTPUTS
    None
 
    .NOTES
    Author: Mark Newton
    License: MIT
    Requires: PowerShell 5.1 or later
 
    .LINK
    https://github.com/MarkusMcNugen/PSWriteColorEX
 
    .LINK
    Get-ColorProfiles
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [Alias('Remove-ColourProfile')]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipeline = $true)]
        [string[]] $Name
    )

    process {
        foreach ($item in $Name) {
            if (-not [PSColorStyle]::Profiles.ContainsKey($item)) {
                $PSCmdlet.WriteError([System.Management.Automation.ErrorRecord]::new(
                    [System.ArgumentException]::new("No profile named '$item'."),
                    'ProfileNotFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $item))
                continue
            }
            if ($PSCmdlet.ShouldProcess($item, 'Remove the style profile')) {
                [PSColorStyle]::Profiles.Remove($item)
            }
        }
    }
}