New-StreamDeckProfile.ps1

function New-StreamDeckProfile
{
    <#
    .Synopsis
        Creates a StreamDeck profile
    .Description
        Creates a StreamDeck profile object
    .Example
        New-StreamDeckProfile -Name Clippy -Action @{
            '0,0' =
                New-StreamDeckAction -Name "Switch Profile" -Setting @{
                    DeviceUUID = ''
                    ProfileUUID = 'A0C89D39-F47D-4CE0-8262-4EE22E22CEFC'
                }
 
            '0,1' = New-StreamDeckAction -HotKey "CTRL+X" -Title "Cut" -Image $home\Downloads\scissors.svg # downloaded from FeatherIcons
 
            '1,1' = New-StreamDeckAction -HotKey "CTRL+C" -Title "Copy" -Image $home\Downloads\copy.svg # downloaded from FeatherIcons
 
            '2,1' = New-StreamDeckAction -HotKey "CTRL+V" -Title "Paste" -Image $home\Downloads\code.svg # downloaded from FeatherIcons
        }
    .Example
        $gitUser = 'StartAutomating'
        $rows, $columns = 2,3
        $repoList = (Invoke-RestMethod -Uri https://api.github.com/users/$gitUser/repos?sort=pushed | ForEach-Object { $_ })
        $n =0
        $actions = [Ordered]@{}
        for ($r = 0 ;$r -lt $rows; $r++) {
            for ($c = 0 ; $c -lt $columns; $C++) {
                $actions["$c,$r"] = New-StreamDeckAction -Uri $repoList[$n].html_url -Title $repoList[$n].name
                $n++
            }
        }
 
        New-StreamDeckProfile -Name GitRepos -Action $actions |
            Save-StreamDeckProfile
    .Link
        Get-StreamDeckProfile
    .Link
        Remove-StreamDeckProfile
    .Link
        Save-StreamDeckProfile
    #>

    [OutputType('StreamDeck.Profile')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Justification="Does not change state")]
    param(
    # The name of the profile
    [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
    [string]
    $Name,

    # A collection of actions.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
    [Collections.IDictionary]
    [ValidateScript({
        foreach ($k in $_.Keys) {
            if ($k -notmatch '\d+,\d+') {
                throw "Action keys must be in the form row, column (e.g. 0,2)."
            }
        }
        return $true
    })]
    $Action,

    # The application identifier.
    # If provided, this profile will be activated whenever this application is given focus.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $AppIdentifier,

    # The device model.
    # If not provided, the most commonly used device model from your other profiles will be used.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $DeviceModel,

    # The device UUID.
    # If not provided, the most commonly used device uuid from your other profiles will be used.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $DeviceUUID,

    # The version of the profile. By default, 1.0
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $Version = '1.0',
    
    # The profile UUID. If not provided, a GUID will be generated.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $ProfileUUID,

    # If provided, will create the profile beneath this directory.
    # If not provided, the profile will be created beneath the user's profiles directory.
    # On Windows, this is: "$env:AppData\Elgato\StreamDeck\ProfilesV2\"
    # On MacOS, this is : "~/Library/Application Support/elgato/StreamDeck/ProfilesV2"
    [string]
    $ProfileRoot,

    # If set, the stream deck profile created will be a child profile, and will not immediately be saved.
    # Child profiles will automatically have an action linking to the parent profile in the upper left.
    [switch]
    $IsChildProfile,

    # If set, the stream deck profile created will be an additional page, and will not immediately be saved.
    # NextPages will automatically have an action linking to the previous page in the lower left.
    [Alias('IsNextPageProfile')]
    [switch]
    $IsNextPage
    )


    process {
        #region Discover Device Model/UUID
        if ($DeviceModel -notmatch '\d{2,}') {
            # If the device model did not contain a set of digits
            $DeviceModel = 
                switch ($DeviceModel) { # assume it's a friendly name. Convert that to it's system name.
                    StreamDeck       { '20GAA9902'  }
                    StreamDeckXL     { '20GAT9901'  } 
                    StreamDeckMini   { '20GAI9901'  } 
                    StreamDeckMobile { 'VSD/WiFi'   }
                    default          { $DeviceModel }
                }
                
            if (-not $DeviceUUID) { # If we do not know the DeviceUUID
                $profiles = Get-StreamDeckProfile
                $DeviceUUID = 
                    $profiles | 
                        Where-Object DeviceModel -EQ $DeviceModel | 
                        Select-Object -ExpandProperty DeviceUUID  -First 1
            }
        }
        if (-not $DeviceModel -and -not $DeviceUUID) {
            $profiles = Get-StreamDeckProfile
            if (-not $DeviceModel) {
                $DeviceModel = $profiles |
                    Group-Object DeviceModel -NoElement |
                    Sort-Object Count -Descending |
                    Select-Object -First 1 -ExpandProperty Name
            }
            if (-not $DeviceUUID) {
                $DeviceUUID = $profiles |
                    Group-Object DeviceUUID -NoElement |
                    Sort-Object Count -Descending |
                    Select-Object -First 1 -ExpandProperty Name
            }
        }
        #endregion Discover Device Model/UUID

        #region Create Profile Object
        $streamDeckProfileObject = [Ordered]@{
            Name=$Name;
            DeviceModel=$DeviceModel
            DeviceUUID=$DeviceUUID
            Guid = if (-not $ProfileUUID) {
                [Guid]::NewGuid().ToString() 
            } else { "$ProfileUUID" }
            Actions=[Ordered]@{}
            PSTypeName = 'StreamDeck.Profile'
            Version = $Version
        }
        #endregion Create Profile Object

        if ($AppIdentifier) {
            $streamDeckProfileObject.AppIdentifier = $AppIdentifier
        }

        #region Determine Profile Root
        if (-not $ProfileRoot) {        
            $profileRoot=
                if ((-not $PSVersionTable.Platform) -or ($PSVersionTable.Platform -match 'Win')) {
                    "$env:AppData\Elgato\StreamDeck\ProfilesV2\"
                } elseif ($PSVersionTable.Platform -eq 'Unix' -and $PSVersionTable.OS -like '*darwin*') {
                    "~/Library/Application Support/elgato/StreamDeck/ProfilesV2"
                }
            $profileDirectory = Join-Path $profileRoot -ChildPath "$($streamDeckProfileObject.Guid).sdProfile"
        } else {
            $profileDirectory = Join-Path (Join-Path $profileRoot -ChildPath "Profiles") -ChildPath "$($streamDeckProfileObject.Guid).sdProfile"
        }

        if (-not ($IsChildProfile -or $IsNextPage)) {
            if (-not (Test-Path $profileDirectory)) {
                $createdDir = New-Item -ItemType Directory -Path $profileDirectory -Force
                if (-not $createdDir) { return }
            }
            $manifestPath = Join-Path $profileDirectory -ChildPath manifest.json
            $streamDeckProfileObject.Path = "$manifestPath"
        }
        #endregion Determine Profile Root

        if ($IsChildProfile) {
            $Action["0,0"] = New-StreamDeckAction -BackToParent
        }

        if ($IsNextPage) {
            $row = 
                if ($DeviceModel -in '20GAA9901', '20GAA9902', 'VSD/WiFi') {
                    2
                }
                elseif ($DeviceModel -in '20GAI9901') {
                    1
                } elseif ($DeviceModel -in '20GAT9901') {
                    3
                } elseif ($DeviceModel -eq 'Corsair G6 Keyboard') {
                    5
                }
            $Action["$row,0"] = New-StreamDeckAction -PreviousPage
        }

        #region Map Actions
        foreach ($act in $Action.GetEnumerator()) {
            $streamDeckProfileObject.Actions[$act.Name] = $act.value
        }
        if ($streamDeckProfileObject.Actions.Count) {
            $streamDeckProfileObject.Actions = [PSCustomobject]$streamDeckProfileObject.Actions
        }
        #endregion Map Actions

        [PSCustomObject]$streamDeckProfileObject
    }
}