Functions/StreamDeck/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, # The name of a StreamDeck plugin template. [Parameter(ValueFromPipelineByPropertyName)] [ArgumentCompleter({ param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeboundParameters) $templateList = Get-StreamDeckPlugin -Template if ($wordToComplete) { $templateList | Where-object { $_.Name -replace '\.StreamDeckPluginTemplate\.ps1$' -like "$wordToComplete*"}| Foreach-Object { $_.Name -replace '\.StreamDeckPluginTemplate\.ps1$' } } else { $templateList | Foreach-Object { $_.Name -replace '\.StreamDeckPluginTemplate\.ps1$' } } })] [string] $Template, [Parameter(ValueFromPipelineByPropertyName)] [Alias('TemplateParameters')] [Collections.IDictionary] $TemplateParameter, [Parameter(ValueFromPipelineByPropertyName)] [Alias('TemplateArguments')] [PSObject[]] $TemplateArgumentList ) process { if (-not $OutputPath) { # If no -OutputPath was provided, $OutputPath = $psBoundParameters['OutputPath'] = Join-Path $pwd "$Name.sdPlugin" # derive one from the name. } if (-not (Test-Path $OutputPath)) { # If there was not an OutputPath, $createdDirectory = New-Item -ItemType Directory -Path $OutputPath # create the directory. if (-not $createdDirectory) { return } } if ($Template) { # If a template was provided $templateCmd = # call Get-StreamDeckPlugin -Template to get the plugin template. @(if ($Template.Contains([io.PATH]::DirectorySeparatorChar)) { Get-StreamDeckPlugin -Template -PluginPath $Template } else { Get-StreamDeckPlugin -Template | Where-Object { $_.Name -replace '\.StreamDeckPluginTemplate\.ps1$' -eq $Template} }) | Select-Object -First 1 if (-not $templateCmd) { # If we were unable to match the template, error out. Write-Error "Unable to find StreamDeckPluginTemplate '$template'" return } $paramCopy = @{} + $PSBoundParameters # Copy our parameters. foreach ($param in @($paramCopy.Keys)) { if (-not $templateCmd.Parameters[$param]) { # If a parameter isn't used by the template $paramCopy.Remove($param) # remove it. } } if ($TemplateParameter -and $TemplateParameter.Count) { # If we've been provided -TemplateParameters foreach ($kv in $TemplateParameter.GetEnumerator()) { # copy those in (overriding any built-in parameters) $paramCopy[$kv.Key] = $kv.Value } } $templateArgs = # If the template was provided with positional arguments, get them ready to pass. if ($TemplateArgumentList) { $TemplateArgumentList } else { @() } # Call the template. $templateOutput = & $templateCmd @paramCopy @templateArgs # If the template output was a dictionary, turn it into a PSObject. if ($templateOutput -is [Collections.IDictionary]) { $templateOutput = [PSCustomObject]$templateOutput } # Any properties set in the output that are parameters to this function foreach ($prop in $templateOutput.psobject.properties) { if (-not $MyInvocation.MyCommand.Parameters[$prop.Name]) { continue } $PSBoundParameters[$prop.Name] = $prop.Value # will be overriden with the value provided from the template. $ExecutionContext.SessionState.PSVariable.Set($prop.Name, $prop.Value) } } # 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') $pluginManifestPath = Join-Path $OutputPath manifest.json $pluginManifest | ConvertTo-Json | Set-Content -Path $pluginManifestPath Get-ChildItem -Path $OutputPath } } |