ytdlWrapper.psm1
# Create some global variables $script:ModuleRoot = $PSScriptRoot $script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\ytdlWrapper.psd1").ModuleVersion $script:DataPath = "$env:APPDATA\Powershell\ytdlWrapper" if ((Test-Path -Path $script:DataPath) -eq $false) { # Create the module data storage folders if they don't exist New-Item -ItemType Directory -Path "$env:APPDATA" -Name "Powershell" -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path "$env:APPDATA\Powershell" -Name "ytdlWrapper" } if ($null -eq (Get-Command "youtube-dl.exe" -ErrorAction SilentlyContinue)) { # Warn the user that youtube-dl.exe cannot be found since without the binary in PATH, # the module won't function correctly. Write-Warning "Could not find youtube-dl.exe in PATH. Install it or modify the PATH variable." } # Detect whether at some level dotsourcing was enforced $script:doDotSource = $global:ModuleDebugDotSource $script:doDotSource = $true #! Needed to make code coverage tests work # Detect whether at some level loading individual module files, rather than the compiled module was enforced $importIndividualFiles = $global:ModuleDebugIndividualFiles # Resolve-Path function which deals with non-existent paths function Resolve-Path_i { [CmdletBinding()] Param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [string] $Path # Path to resolve ) # Run the command silently $resolvedPath = Resolve-Path $Path -ErrorAction SilentlyContinue # Variable will be null if $Path doesn't exist # In that case set it to an empty string if ($null -eq $resolvedPath) { $resolvedPath = "" } $resolvedPath } # If script detects its running from original dev environment, import individually since module won't be compiled if (Test-Path (Resolve-Path_i -Path "$($script:ModuleRoot)\..\.git")) { $importIndividualFiles = $true } if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true } # Imports a module file, either through dot-sourcing or through invoking the script function Import-ModuleFile { <# .SYNOPSIS Loads files into the module on module import. .DESCRIPTION This helper function is used during module initialization. It should always be dotsourced itself, in order to proper function. This provides a central location to react to files being imported, if later desired .PARAMETER Path The path to the file to load .EXAMPLE PS C:\> . Import-ModuleFile -File $function.FullName Imports the file stored in $function according to import policy #> [CmdletBinding()] Param ( [string] $Path # Path of module file ) # Get the resolved path to avoid any cross-OS issues $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath if ($doDotSource) { # Load the script through dot-sourcing . $resolvedPath }else { # Load the script through different method (unknown atm) $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) } } # Load individual files if not compiled if ($importIndividualFiles) { # Execute Preimport actions . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\preimport.ps1" # Import all internal functions foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Import all public functions foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Execute Postimport actions . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\postimport.ps1" # End execution here, do not load compiled code below return } #region Load compiled code function Get-Jobs { <# .SYNOPSIS Get and return a list of jobs .DESCRIPTION Get and return a list of youtube-dl.Job objects from the database file. .PARAMETER Path The path of the database file. .EXAMPLE PS C:\> $jobList = Get-Job -Path "%appdata%/database.xml" Populates the array/list with all jobs in the specified database. .INPUTS None .OUTPUTS None .NOTES #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path ) $jobList = [System.Collections.Generic.List[psobject]]@() # If the file doesn't exist, then the import logic will error accordingly if ((Test-Path -Path $Path) -eq $true) { # Read the xml data in $xmlData = Import-Clixml -Path $Path foreach ($item in $xmlData) { # Rather than extracting the deserialised objects, which would create a mess of serialised and non-serialised objects # Create new identical copies from scratch if ($item.PSObject.TypeNames[0] -eq "Deserialized.youtube-dl.Job") { $job = New-Object -TypeName psobject $job.PSObject.TypeNames.Insert(0, "youtube-dl.Job") # Copy the properties from the Deserialized object into the new one foreach ($property in $item.PSObject.Properties) { # Copy over the deserialised object properties over to new object if ($property.Name -eq "Scriptblock") { # In the case of a scriptblock, create it as a proper scriptblock object so that it doesn't # have to be converted later on, possibly more than once? $job | Add-Member -Type NoteProperty -Name "Scriptblock" -Value ([Scriptblock]::Create($property.Value)) }else { $job | Add-Member -Type NoteProperty -Name $property.Name -Value $property.Value } } $jobList.Add($job) } } } # Return the list as a List object, rather than as an array (by default) Write-Output $jobList -NoEnumerate } function Read-ConfigDefinitions { <# .SYNOPSIS Read definitions from a config file .DESCRIPTION Read in either input definitions, variable definitions, or variable scriptblock declerations from a youtube-dl configuration file. .EXAMPLE PS C:\> Read-ConfigDefinitions -Path "~/conf.txt" -InputDefinitions Reads in and generates a list of all input definition names. .INPUTS None .OUTPUTS System.Collections.Generic.List[System.String] .NOTES #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $Path, [Parameter()] [switch] $InputDefinitions, [Parameter()] [switch] $VariableDefinitions, [Parameter()] [switch] $VariableScriptblocks ) # Read in the config file as a single string $configFilestream = Get-Content -Path $Path -Raw $definitionList = [System.Collections.Generic.List[System.String]]@() if ($InputDefinitions -eq $true) { # Find all matches to: # 1. --some-parameter i@{name} : normal parameter definition # 1. -s i@{name} : shorthand parameter definition # 2. 'i@{name}' : special case for url, since it doesn't have a flag # Also matches even if multiple parameter definitions are on the same line $regex1 = [regex]::Matches($configFilestream, "(-(\S+)\s'?i@{(\w+)}'?)\s*") $regex2 = [regex]::Matches($configFilestream, "('i@{(\w+)}')") # Add the descriptor fields to the list foreach ($match in $regex1) { # .Group[1] is the whole match # .Group[2] is the 'some-parameter' or 's' match # .Group[3] is the 'name' match $definitionList.Add($match.Groups[3].Value) } foreach ($match in $regex2) { # .Group[1] is the whole match # .Group[2] is the 'name' match $definitionList.Add($match.Groups[2].Value) } }else { # Find all matches to: # 1. --some-parameter v@{name}{start{scriptblock}end} : normal parameter definition # 1. -s v@{name}{start{scritpblock}end} : shorthand parameter definition # Also matches even if multiple parameter definitions are on the same line $regex = [regex]::Matches($configFilestream, "(-(\S+)\s'?v@{(\w+)}{start{(?s)(.*?)}end}'?)\s+") # Add the descriptor fields to the list foreach ($match in $regex) { # .Group[1] is the whole match # .Group[2] is the 'some-parameter' or 's' match # .Group[3] is the 'name' match # .Group[4] is the 'scriptblock' match if ($VariableDefinitions -eq $true) { $definitionList.Add($match.Groups[3].Value) }elseif ($VariableScriptblocks -eq $true) { $definitionList.Add($match.Groups[4].Value) } } } # Return the list as a List object, rather than as an array (by default) Write-Output $definitionList -NoEnumerate } function Write-Message { <# .SYNOPSIS Writes a message to the screen. .DESCRIPTION Writes a message to the screen, as text, a warning, or an error. .PARAMETER Message The message to print to screen. .PARAMETER DisplayText Writes the message as standard text. .PARAMETER DisplayWarning Writes the message as a warning. .PARAMETER DisplayError Writes the message as an error. .EXAMPLE PS C:\> Write-Message -Message "invalid argument" -DisplayError Prints the error message to screen by invoking Write-Error. #> [CmdletBinding()] Param( [Parameter(ParameterSetName = "DisplayText", Mandatory = $true)] [Parameter(ParameterSetName = "DisplayWarning", Mandatory = $true)] [Parameter(ParameterSetName = "DisplayError", Mandatory = $true)] [string] $Message, [Parameter(ParameterSetName = "DisplayText", Mandatory = $true)] [switch] $DisplayText, [Parameter(ParameterSetName = "DisplayWarning", Mandatory = $true)] [switch] $DisplayWarning, [Parameter(ParameterSetName = "DisplayError", Mandatory = $true)] [switch] $DisplayError ) if ($DisplayText -eq $true) { Write-Host -Message $Message }elseif ($DisplayWarning -eq $true) { Write-Warning -Message $Message }elseif ($DisplayError -eq $true) { Write-Error -Message $Message } } function Add-YoutubeDLJob { <# .SYNOPSIS Create a new job definition .DESCRIPTION Add a new youtube-dl job definition to the database, which can be used with the Invoke-YoutubeDL command. .PARAMETER Name The name to call the job. .PARAMETER ConfigPath The filepath pointing to the configuration file. .PARAMETER Scriptblock A scriptblock which will be executed as part of the job once youtube-dl finishes running. .EXAMPLE PS C:\> Add-YoutubeDLJob -Name "test" -ConfigPath ~/conf.txt -Number "123" Adds a new job under the name "test", pointing to the configuration file specified, which contains a variable "Number" and initialises it with the value "123". .INPUTS None .OUTPUTS None .NOTES Once you supply a valid configuration filepath, the function will create parameters at runtime for each variable found in the file, so if for example the configuration file has the variables: "number", "url", the parameters -Number and -Url will be exposed. To see all the parameters, pressing Ctrl+Tab will show the variable parameters at the top of the list. #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory = $true)] [string] $Name, [Parameter(Position = 1, Mandatory = $true)] [Alias("Path")] [string] $ConfigPath, [Parameter(Position = 2, Mandatory = $false)] [scriptblock] $Scriptblock ) dynamicparam { # Only run the logic if the file exists if ((Test-Path -Path $ConfigPath) -eq $true) { # Retrieve all instances of variable definitions in the config file $definitionList = Read-ConfigDefinitions -Path $ConfigPath -VariableDefinitions #Define the dynamic parameter dictionary to add all new parameters to $parameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Now that a list of all variable definitions is found, create a dynamic parameter for each foreach ($definition in $definitionList) { $paramAttribute = New-Object System.Management.Automation.ParameterAttribute $paramAttribute.Mandatory = $true $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($paramAttribute) $param = New-Object System.Management.Automation.RuntimeDefinedParameter($definition, [String], $attributeCollection) $parameterDictionary.Add($definition, $param) } return $parameterDictionary } } process { # Read in the list of job objects $jobList = Get-Jobs -Path "$script:DataPath\database.xml" # Check that the job name isn't already taken $job = $jobList | Where-Object { $_.Name -eq $Name } if ($null -ne $job) { Write-Message -Message "There already exists a job called: $Name" -DisplayWarning return } # Ensure the config file actually exists if ((Test-Path -Path $ConfigPath) -eq $false) { Write-Message -Message "There is no file located at: $ConfigPath" -DisplayWarning return } # Retrieve all instances of variable definitions in the config file $definitionList = Read-ConfigDefinitions -Path $ConfigPath -VariableDefinitions # Set up the job object $job = New-Object -TypeName psobject $job.PSObject.TypeNames.Insert(0, "youtube-dl.Job") $job | Add-Member -NotePropertyName "Name" -NotePropertyValue $Name $job | Add-Member -NotePropertyName "ConfigPath" -NotePropertyValue $ConfigPath # Add the user-provided variable initial-values to the job object [hashtable]$variableList = [ordered]@{} foreach ($definition in $definitionList) { $variableList.Add($definition, $PSBoundParameters[$definition]) } # If a scriptblock has been given in, add it to the job object if ($null -ne $Scriptblock) { $job | Add-Member -NotePropertyName "Scriptblock" -NotePropertyValue $Scriptblock.ToString() } $job | Add-Member -NotePropertyName "Variables" -NotePropertyValue $variableList $jobList.Add($job) # Save the newly created job to the database file Export-Clixml -Path "$script:DataPath\database.xml" -InputObject $jobList | Out-Null } } function Get-YoutubeDLJob { <# .SYNOPSIS Get a job definition .DESCRIPTION Return a youtube-dl job definition object. If run as a standalone command, it will write the job details to the screen. .PARAMETER JobName The name of the job to retrieve. Accepts multiple names in an array. .EXAMPLE PS C:\> Get-YoutubeDLJob -JobName "test" Returns the youtube-dl job object for the job named "test". .EXAMPLE PS C:\> "test","test2" | Get-YoutubeDLJob Returns the youtube-dl job objects for the jobs named "test" and "test2" one after another. .INPUTS System.String[] .OUTPUTS youtube-dl.Job[] .NOTES #> [CmdletBinding()] param ( # Tab completion [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline)] [Alias("Job","Name")] [string[]] $JobName ) process { foreach ($name in $JobName) { # Read in the list of job objects $jobList = Get-Jobs -Path "$script:DataPath\database.xml" # Check that the job exists $job = $jobList | Where-Object { $_.Name -eq $name } if ($null -eq $job) { Write-Message -Message "There is no job called: $name" -DisplayWarning return } Write-Output $job } } } function Invoke-YoutubeDL { <# .SYNOPSIS Invoke youtube-dl .DESCRIPTION Invoke the youtube-dl process, specifying either an already defined job or a configuration file. .PARAMETER ConfigPath The filepath pointing to the configuration file to use. .PARAMETER JobName The name of the job to run. .EXAMPLE PS C:\> Invoke-YoutubeDL -ConfigPath "~/conf.txt" -Url "//some/url/" Invokes youtube-dl using the specified configuration path, with has an input definition "Url" that is passed in as a parameter. .EXAMPLE PS C:\> Invoke-YoutubeDL -JobName "test" Invokes youtube-dl using the configuration path specified by the job, and any variables which may be defined for this job. .INPUTS None .OUTPUTS None .NOTES #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory = $true, ParameterSetName = "Config")] [Alias("Path")] [string] $ConfigPath, # Tab completion [Parameter(Position = 0, Mandatory = $true, ParameterSetName = "Job")] [Alias("Job", "Name")] [string] $JobName ) dynamicparam { # Only run the logic if the file exists if ($null -ne $ConfigPath -and (Test-Path $ConfigPath) -eq $true) { # Retrieve all instances of input definitions in the config file $definitionList = Read-ConfigDefinitions -Path $ConfigPath -InputDefinitions #Define the dynamic parameter dictionary to add all new parameters to $parameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Now that a list of all input definitions is found, create a dynamic parameter for each foreach ($definition in $definitionList) { $paramAttribute = New-Object System.Management.Automation.ParameterAttribute $paramAttribute.Mandatory = $true $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($paramAttribute) $param = New-Object System.Management.Automation.RuntimeDefinedParameter($definition, [String], $attributeCollection) $parameterDictionary.Add($definition, $param) } return $parameterDictionary } } process { if ($PSCmdlet.ParameterSetName -eq "Config") { # Ensure the config file actually exists if ((Test-Path -Path $ConfigPath) -eq $false) { Write-Message -Message "There is no file located at: $ConfigPath" -DisplayWarning return } $configFileContent = Get-Content -Path $ConfigPath -Raw # Retrieve all instances of input definitions in the config file $definitionList = Read-ConfigDefinitions -Path $ConfigPath -InputDefinitions foreach ($definition in $definitionList) { if ($PSBoundParameters.ContainsKey($definition) -eq $true) { # Replace the occurence of the input definition with the user provided value $configFileContent = $configFileContent -replace "i@{$definition}", $PSBoundParameters[$definition] }else { # Warn the user and exit if they've not specified one of the input definition parameters Write-Message -Message "You have not supplied the following user input: $definition" -DisplayWarning return } } # Write modified config file (with user inputs) to a temp file # It is easier to read in the config file than edit the existing string to work properly, by surrounding stuff in "" quotes etc Out-File -FilePath "$script:DataPath\temp.conf" -Force -InputObject $configFileContent } if ($PSCmdlet.ParameterSetName -eq "Job") { # Retrieve the job and heck that it exists $jobList = Get-Jobs -Path "$script:DataPath\database.xml" $job = $jobList | Where-Object { $_.Name -eq $JobName } if ($null -eq $job) { Write-Message -Message "There is no job called: $JobName" -DisplayWarning return } # Read in the contents of the job config file $configFileContent = Get-Content -Path $job.ConfigPath -Raw # Retrieve all instances of variable definitions in the config file $definitionList = Read-ConfigDefinitions -Path $job.ConfigPath -VariableDefinitions # Check that the job variables match the configuration file definitions, otherwise there would be errors $jobDefinitionList = $job.Variables.Keys $difference1 = $jobDefinitionList | Where-Object { $definitionList -notcontains $_ } $difference2 = $definitionList | Where-Object { $jobDefinitionList -notcontains $_ } if (($null -ne $difference1) -or ($null -ne $difference2)) { Write-Message -Message "The job variables in the database do not match the variable definitions in the configuration file. `rRun Set-YoutubeDLJob with the -Update switch to fix the issue. See docs for help." -DisplayWarning return } foreach ($definition in $definitionList) { # Replace the occurence of the variable definition with the variable value from the database $configFileContent = $configFileContent -replace "v@{$definition}{start{(?s)(.*?)}end}", $job.Variables[$definition] } # Retrieve all instances of variable scriptblocks in the config file $scriptblockDefinitionList = Read-ConfigDefinitions -Path $job.ConfigPath -VariableScriptblocks # Create a table linking each scriptblock to its respective definition name [hashtable]$scriptblockList = [ordered]@{} for ($i = 0; $i -lt $definitionList.Count; $i++) { $scriptblockList.Add($definitionList[$i], [scriptblock]::Create($scriptblockDefinitionList[$i])) } # Write modified config file (with user inputs) to a temp file # It is easier to read in the config file than edit the existing string to work properly, by surrounding stuff in "" quotes etc Out-File -FilePath "$script:DataPath\temp.conf" -Force -InputObject $configFileContent } # Define youtube-dl process information $processStartupInfo = New-Object System.Diagnostics.ProcessStartInfo -Property @{ FileName = "youtube-dl" Arguments = "--config-location `"$script:DataPath\temp.conf`"" UseShellExecute = $false } # Start and wait for youtube-dl to finish $process = New-Object System.Diagnostics.Process $process.StartInfo = $processStartupInfo $process.Start() $process.WaitForExit() $process.Dispose() # Delete the temp config file since its no longer needed Remove-Item -Path "$script:DataPath\temp.conf" -Force # Execute any scriptblocks for variables if ($PSCmdlet.ParameterSetName -eq "Job") { # Run every scriptblock, and store the result back into the databasee foreach ($key in $scriptblockList.Keys) { $returnResult = Invoke-Command -ScriptBlock $scriptblockList[$key] if ($null -eq $returnResult) { Write-Message -Message "The scriptblock for the $key variable definition didn't return a value. It must return a value." -DisplayError return } $job.Variables[$key] = $returnResult } # If the job has a scriptblock, run it if ($null -ne $job.Scriptblock) { Invoke-Command -ScriptBlock $job.Scriptblock -ArgumentList $job } # Save the modified job (if any scriptblocks ran) to the database file Export-Clixml -Path "$script:DataPath\database.xml" -InputObject $jobList | Out-Null } } } function Remove-YoutubeDLJob { <# .SYNOPSIS Remove a job definition .DESCRIPTION Remove a youtube-dl job definition from the database. .PARAMETER JobName The name of the job to remove. Accepts multiple names in an array. .EXAMPLE PS C:\> Remove-YoutubeDLJob -JobName "test" Removes a job called "test" from the database. .EXAMPLE PS C:\> "test","test2" | Remove-YoutubeDLJob Removes the jobs called "test" and "test2" from the database. .INPUTS System.String[] .OUTPUTS None .NOTES #> [CmdletBinding()] param ( # Tab completion [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline)] [Alias("Job")] [string[]] $JobName ) process { foreach ($name in $JobName) { # Read in the list of job objects $jobList = Get-Jobs -Path "$script:DataPath\database.xml" # Check that the job exists $job = $jobList | Where-Object { $_.Name -eq $name } if ($null -eq $job) { Write-Message -Message "There is no job called: $name" -DisplayWarning return } $jobList.Remove($job) # Save the modified database file with the job removed changes Export-Clixml -Path "$script:DataPath\database.xml" -InputObject $jobList | Out-Null } } } function Set-YoutubeDLJob { <# .SYNOPSIS Set a property of a job .DESCRIPTION Set a property of a youtube-dl job definition, such as the configurataion filepath, or a variable value. This command also allows to sync up the job variables to the definitions in the config file, if the configuration file has been modified, i.e. variables added/removed. .PARAMETER JobName The name of the job to configure. Accepts multiple names in an array. .PARAMETER Variable The variable to edit. .PARAMETER Value The new value for the variable. .PARAMETER ConfigPath The new filepath pointing to the configuration file. .PARAMETER Scriptblock The new scriptblock to use post-execution. .PARAMETER Update Sync the job variable definitions to the definitions found in the configuration file. Use this switch if the variables have been added/removed from the configuration file. .EXAMPLE PS C:\> Set-YoutubeDLJob -JobName "test" -Variable "number" -Value "123" Sets the variable "number" to the new value of "123" for the job named "test". .EXAMPLE PS C:\> Set-YoutubeDLJob -JobName "test" -ConfigPath "~/new-config.txt" Sets the configuration filepath for the job named "test". .EXAMPLE PS C:\> Set-YoutubeDLJob -JobName "test" -Scriptblock $script Sets the scriptblock for the job named "test" to the scriptblock $script. .EXAMPLE PS C:\> Set-YoutubeDLJob -JobName "test" -Update -NewVariable "value" Sets the value for the non-initialised variable "NewVariable" to "value", for the job "test". .INPUTS System.String[] .OUTPUTS None .NOTES When using the -Update switch, once a valid job name has been supplied, the function will create parameters at runtime for each new (non-initialised) variable found in the configuration file, so if for example the configuration file has the new variable: "NewVariable" added to it, the parameter -NewVariable will be exposed.To see all the parameters, pressing Ctrl+Tab will show the variable parameters at the top of the list. The function will also automatically delete any records of variables that are no longer present in the configuration file. #> [CmdletBinding()] param ( # Tab completion [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName, ParameterSetName = "Variable")] [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName, ParameterSetName = "Config")] [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName, ParameterSetName = "Scriptblock")] [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName, ParameterSetName = "Update")] [Alias("Job", "Name")] [string] $JobName, # Tab completion once jobname is given [Parameter(Position = 1, Mandatory = $true, ParameterSetName = "Variable")] [string] $Variable, [Parameter(Position = 1, Mandatory = $true, ParameterSetName = "Config")] [string] $ConfigPath, [Parameter(Position = 1, Mandatory = $true, ParameterSetName = "Scriptblock")] [scriptblock] $Scriptblock, [Parameter(Position = 1, Mandatory = $true, ParameterSetName = "Update")] [switch] $Update, [Parameter(Position = 2, Mandatory = $true, ParameterSetName = "Variable")] [string] $Value ) dynamicparam { # Read in the list of job objects and try to get the job $jobList = Get-Jobs -Path "$script:DataPath\database.xml" $job = $jobList | Where-Object { $_.Name -eq $JobName } # Only run if a valid job name has been given and the -Update switch is on if (($null -ne $job) -and ($Update -eq $true)) { # Get a list of definitions stored in the job in the config file $jobDefinitions = $job.Variables.Keys $configDefinitions = Read-ConfigDefinitions -Path $job.ConfigPath -VariableDefinitions # Find a list of variables which need to be added $variablesToAdd = $configDefinitions | Where-Object { $jobDefinitions -notcontains $_ } #Define the dynamic parameter dictionary to add all new parameters to $parameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Now that a list of all new variable definitions is found, create a dynamic parameter for each foreach ($variable in $variablesToAdd) { $paramAttribute = New-Object System.Management.Automation.ParameterAttribute $paramAttribute.Mandatory = $true $paramAttribute.ParameterSetName = "Update" $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($paramAttribute) $param = New-Object System.Management.Automation.RuntimeDefinedParameter($variable, [String], $attributeCollection) $parameterDictionary.Add($variable, $param) } return $parameterDictionary } } process { # Read in the list of job objects $jobList = Get-Jobs -Path "$script:DataPath\database.xml" # Check that the job exists $job = $jobList | Where-Object { $_.Name -eq $JobName } if ($null -eq $job) { Write-Message -Message "There is no job called: $JobName" -DisplayWarning return } if ($PSCmdlet.ParameterSetName -eq "Variable") { # Check that the variable is valid and exists if ($job.Variables.ContainsKey($Variable) -eq $false) { Write-Message -Message "There is no variable called: $Variable for the job: $JobName" -DisplayWarning return } # Set the variable value to the newly specified value $job.Variables[$Variable] = $Value }elseif ($PSCmdlet.ParameterSetName -eq "Config") { # Set the configuration filepath to the new value $job.ConfigPath = $ConfigPath }elseif ($PSCmdlet.ParameterSetName -eq "Scriptblock") { # Set the scriptblock to the new one, if there is no previously assigned scriptblock create a new one if ($null -ne $job.Scriptblock) { $job.Scriptblock = $Scriptblock.ToString() }else { $job | Add-Member -NotePropertyName "Scriptblock" -NotePropertyValue $Scriptblock.ToString() } }elseif ($PSCmdlet.ParameterSetName -eq "Update") { # Get a list of definitions stored in the job in the config file $jobDefinitions = $job.Variables.Keys $configDefinitions = Read-ConfigDefinitions -Path $job.ConfigPath -VariableDefinitions # Find a list of variables which need to be removed and remove them all $variablesToRemove = $jobDefinitions | Where-Object { $configDefinitions -notcontains $_ } foreach ($variable in $variablesToRemove) { $job.Variables.Remove($variable) } # Find a list of variables which need to be added and add them from the user passed in parameters $variablesToAdd = $configDefinitions | Where-Object { $jobDefinitions -notcontains $_ } foreach ($variable in $variablesToAdd) { $job.Variables.Add($variable, $PSBoundParameters[$variable]) } } # Save the modified database file with the job changes Export-Clixml -Path "$script:DataPath\database.xml" -InputObject $jobList | Out-Null } } # Scriptblocks used for tab expansion assignments $argCompleter_JobName = { param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) # Import all youtube-dl.Job objects from the database file $jobList = Get-Jobs -Path "$script:DataPath\database.xml" if ($jobList.Count -eq 0) { Write-Output "" } # Return the names which match the currently typed in pattern $jobList.Name | Where-Object { $_ -like "$wordToComplete*" } } $argCompleter_JobVariable = { param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) # Get the already typed in job name $jobName = $fakeBoundParameters.JobName if ($null -ne $jobName) { # Import all youtube-dl.Job objects from the database file $jobList = Get-Jobs -Path "$script:DataPath\database.xml" $job = $jobList | Where-Object { $_.Name -eq $jobName } if ($null -ne $job) { # Return the variables which match currently typed in pattern $job.Variables.Keys | Where-Object { $_ -like "$wordToComplete*" } } } } # Tab expansion assignements for commands Register-ArgumentCompleter -CommandName Invoke-YoutubeDL -ParameterName JobName -ScriptBlock $argCompleter_JobName Register-ArgumentCompleter -CommandName Remove-YoutubeDLJob -ParameterName JobName -ScriptBlock $argCompleter_JobName Register-ArgumentCompleter -CommandName Get-YoutubeDLJob -ParameterName JobName -ScriptBlock $argCompleter_JobName Register-ArgumentCompleter -CommandName Set-YoutubeDLJob -ParameterName JobName -ScriptBlock $argCompleter_JobName Register-ArgumentCompleter -CommandName Set-YoutubeDLJob -ParameterName Variable -ScriptBlock $argCompleter_JobVariable #endregion Load compiled code |