Private/Get-HDTConsoleApplicationEdit.ps1
|
function Get-HDTConsoleApplicationEdit { <# .SYNOPSIS Turns what was typed into one of the console's application rows into the Set-HDTApplication argument that writes it. .DESCRIPTION THE PANE WRITES THROUGH Set-HDTApplication, AND NOT EVERY ROW IS A STRING. A name, a publisher and a command line are typed and passed straight on; successCodes and rebootCodes are int[], dependencies is string[], and detect is a whole block. Something has to stand between the box and the cmdlet, and putting it here rather than in the window is what makes it testable without a window. THE PARAMETER IS NOT THE KEY, AND THAT IS NOT A DETAIL. app.yaml says successCodes and dependencies; the cmdlet takes -SuccessCode and -Dependency, singular, because that is the rule for a PowerShell parameter. The window used to capitalise the key and splat it, which works for exactly the rows that happen to agree - and would call a parameter that does not exist for the four that do not. AN EMPTY BOX IS AN ANSWER, NOT A BLANK. Set-HDTApplication removes a key given an empty list or an empty hashtable, and removing is what an administrator means when they clear the box: no dependencies, no detection rule, and for the exit codes the defaults DESIGN 8 states - 0 and 3010 for success, 3010 for reboot. It does not mean "no code succeeds", which is why the row shows the inherited pair rather than an empty box in the first place. THE DETECTION RULE IS PARSED AS YAML, BY THE PARSER THAT READS app.yaml. A second dialect - key=value, or JSON in a box - would be a second thing to learn and a second thing to get wrong, and the block in the box is the block in the document. .PARAMETER Property The document key the row writes: 'install', 'successCodes', 'dependencies', 'detect' and so on. .PARAMETER Text What was typed. .INPUTS None. This command does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject with Parameter, the Set-HDTApplication parameter name; Value, what to pass it; and Text, that value written the way it would be typed, for the command the console's footer echoes. DESIGN 12: every edit names the command that would repeat it, and a list echoed as a string teaches a line that would not run. .EXAMPLE Get-HDTConsoleApplicationEdit -Property 'successCodes' -Text '0, 3010' .EXAMPLE Get-HDTConsoleApplicationEdit -Property 'detect' -Text "type: msiProduct`nproductCode: '{23170F69}'" #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [string] $Property, [Parameter(Mandatory = $true, Position = 1)] [AllowEmptyString()] [string] $Text ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # THE LABEL, SO A REFUSAL NAMES THE ROW THE TECHNICIAN IS LOOKING AT. '0, ok # is not a number' is a puzzle; 'Success codes: ok is not a number' is an # instruction. $label = @{ successCodes = 'Success codes' rebootCodes = 'Reboot codes' dependencies = 'Depends on' detect = 'Detection' } $split = { param([string] $Value) return @($Value -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }) } # SINGLE QUOTES AND A DOUBLED ONE INSIDE, which is PowerShell's own rule and # the only one that survives an apostrophe in a display name. $quote = { param([string] $Value) return ("'" + ($Value -replace "'", "''") + "'") } if (($Property -eq 'successCodes') -or ($Property -eq 'rebootCodes')) { $parameter = 'SuccessCode' if ($Property -eq 'rebootCodes') { $parameter = 'RebootCode' } $number = New-Object -TypeName System.Collections.ArrayList foreach ($current in (& $split $Text)) { $parsed = 0 if (-not [int]::TryParse($current, [ref] $parsed)) { throw (New-Object System.ArgumentException ( "{0}: '{1}' is not a number. An exit code list is whole numbers separated by commas - 0, 3010." -f $label[$Property], $current)) } [void] $number.Add($parsed) } return [pscustomobject] @{ Parameter = $parameter Value = [int[]] @($number) Text = ('@({0})' -f (@($number) -join ', ')) } } if ($Property -eq 'dependencies') { $id = @(& $split $Text) return [pscustomobject] @{ Parameter = 'Dependency' Value = [string[]] $id Text = ('@({0})' -f ((@($id) | ForEach-Object { & $quote $_ }) -join ', ')) } } if ($Property -eq 'detect') { if ([string]::IsNullOrWhiteSpace($Text)) { return [pscustomobject] @{ Parameter = 'Detect'; Value = @{}; Text = '@{ }' } } $parsed = $null try { $parsed = ConvertFrom-HDTYaml -Yaml $Text -Path 'the detection rule' } catch { throw (New-Object System.ArgumentException ( "Detection: the detection rule could not be read. {0}" -f $_.Exception.Message)) } if (-not ($parsed -is [System.Collections.IDictionary])) { throw (New-Object System.ArgumentException ( "Detection: the detection rule is a block of keys, one per line - 'type: msiProduct' and then the keys that type takes. What was typed is not one.")) } $pair = @($parsed.Keys | ForEach-Object { '{0} = {1}' -f $_, (& $quote ([string] $parsed[$_])) }) return [pscustomobject] @{ Parameter = 'Detect' Value = $parsed Text = ('@{{ {0} }}' -f ($pair -join '; ')) } } # EVERY OTHER ROW IS A STRING WHOSE KEY IS THE PARAMETER, one capital apart: # name, description, publisher, version, folder, install, uninstall, runIn. return [pscustomobject] @{ Parameter = $Property.Substring(0, 1).ToUpperInvariant() + $Property.Substring(1) Value = $Text Text = (& $quote $Text) } } |