New-StreamDeckPlugin.ps1

function New-StreamDeckPlugin
{
    <#
    .Synopsis
        Creates a StreamDeck Plugin
    .Description
        Creates a new StreamDeck Plugin.
    .Link
        https://developer.elgato.com/documentation/stream-deck/sdk/manifest/
    .Link
        Get-StreamDeckPlugin
    #>

    param(
    # The name of the plugin. This string is displayed to the user in the Stream Deck store.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
    [string]
    $Name,

    # The author of the plugin. This string is displayed to the user in the Stream Deck store.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
    [string]
    $Author,

    # Specifies an array of actions. A plugin can indeed have one or multiple actions.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateScript({
        $validPropertyNames = 'Icon','Name','PropertyInspectorPath','States','SupportedInMultiActions','Tooltip'
        foreach ($prop in $_.psobject.properties) {
            
            if ($prop.name -notin $validPropertyNames) {
                throw "$($prop.Name) is not allowed. Valid properties are: $($validPropertyNames -join ' , ')"
            }
        }
    })]
    [Alias('Actions')]
    [PSObject[]]
    $Action,

    # Provides a general description of what the plugin does.
    # This is displayed to the user in the Stream Deck store.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
    [string]
    $Description,

    # The version of the plugin which can only contain digits and periods. This is used for the software update mechanism.
    [Parameter(ValueFromPipelineByPropertyName)]
    [Version]
    $Version = "0.1",

    # The relative path to a PNG image without the .png extension.
    # This image is displayed in the Plugin Store window.
    # The PNG image should be a 72pt x 72pt image.
    # You should provide @1x and @2x versions of the image.
    # The Stream Deck application takes care of loading the appropriate version of the image.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
    [string]
    $Icon,

    # The name of the custom category in which the actions should be listed.
    # This string is visible to the user in the actions list.
    # If you don't provide a category, the actions will appear inside a "Custom" category.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $Category,

    # The relative path to a PNG image without the .png extension.
    # This image is used in the actions list.
    # The PNG image should be a 28pt x 28pt image.
    # You should provide @1x and @2x versions of the image.
    # The Stream Deck application takes care of loading the appropriate version of the image.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $CategoryIcon,

    # The relative path to the HTML/binary file containing the code of the plugin.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $CodePath,

    # Override CodePath for Windows.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $CodePathWin,

    # Override CodePath for macOS.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $CodePathMac,

    # The relative path to the Property Inspector html file if your plugin want to display some custom settings in the Property Inspector.
    # If missing, the plugin will have an empty Property Inspector.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $PropertyInspectorPath,

    # Specify the default window size when a Javascript plugin or Property Inspector opens a window using window.open().
    # Default value is [500, 650].
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $DefaultWindowSize,

    # The list of operating systems supported by the plugin as well as the minimum supported version of the operating system.
    [Parameter(ValueFromPipelineByPropertyName)]
    [PSObject[]]
    $OS,

    # Indicates which version of the Stream Deck application is required to install the plugin.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateScript({
        if (-not $_.MinimumVersion) {
            throw "Must have minimum version"
        }
    })]
    [PSObject]
    $Software = @{MinimumVersion='4.1'},

    # A URL displayed to the user if he wants to get more info about the plugin.
    [Parameter(ValueFromPipelineByPropertyName)]
    [uri]
    $Url,

    # List of application identifiers to monitor (applications launched or terminated).
    # See the applicationDidLaunch and applicationDidTerminate events.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateScript({
        if ($_ -is [Collections.IDictionary]) {
            foreach ($k in $_.keys) {
                if ($k -cnotin 'mac', 'windows') {
                    throw "Property names must be 'mac' or 'windows'"
                }
                if ($_[$k] -isnot [Object[]]) {
                    throw "Property values must be an array"
                }
            }
        } else {
            foreach ($prop in $_.psobject.properties) {
                if ($prop.Name -cnotin 'mac', 'windows') {
                    throw "Property names must be 'mac' or 'windows'"
                }
                if ($prop.Value -isnot [Object[]]) {
                    throw "Property values must be an array"
                }
            }
        }
        return $true
    })]
    [PSObject]
    $ApplicationsToMonitor,

    # Specifies an array of profiles.
    # A plugin can indeed have one or multiple profiles that are proposed to the user on installation.
    # This lets you create fullscreen plugins.
    [Parameter(ValueFromPipelineByPropertyName)]
    [PSObject[]]
    $Profiles,

    # The output path.
    # If not provided, the plugin will be created in a directory beneath the current directory.
    # This directory will be named $Name.sdPlugin.
    [string]
    $OutputPath
    )

    process {
        # Create the manifest template.
        $pluginManifest = 
            [Ordered]@{
                Name        = $Name
                Version     = "$Version"
                Description = $Description
                Actions     = $Action
                Author      = $Author
                SDKVersion  = 2
                Software    = $Software
            }

        $generateScript = $false
        if (-not $CodePath) {
            $CodePath = "StartPlugin.cmd"
        }

        :nextParam foreach ($param in (
                    [Management.Automation.CommandMetaData]$MyInvocation.MyCommand
                ).Parameters.GetEnumerator()
        ) {
            if ($pluginManifest.Keys -contains $param.Key) { continue }
            foreach ($k in $pluginManifest.Keys) {
                if ($param.Value.Aliases -and $param.Value.Aliases -contains $k) {
                    continue nextParam
                }
            }
            $ParamVar = $ExecutionContext.SessionState.PSVariable.Get($param.Key)
            if ($ParamVar.Value -ne $null) {
                $pluginManifest[$param.Key] = $ParamVar.Value
            }
        }

        # Clear out any blank values from the manifest
        foreach ($k in @($pluginManifest.Keys)) {
            if (-not $pluginManifest[$k]) {
                $pluginManifest.Remove($k)
            }
        }

        # Remove the output path from the manifest
        # (it will end up there as a side-effect of the previous code, and it's not a part of the 'schema')
        $pluginManifest.Remove('OutputPath')

        if (-not $OutputPath) {
            $OutputPath = Join-Path $pwd "$Name.sdPlugin"
        }
        if (-not (Test-Path $OutputPath)) {
            $createdDirectory = New-Item -ItemType Directory -Path $OutputPath
            if (-not $createdDirectory) { return }
        }
        $pluginManifestPath = Join-Path $OutputPath manifest.json
        $pluginManifest | ConvertTo-Json | Set-Content -Path $pluginManifestPath
        Get-Item $pluginManifestPath
    }
}