Public/Add-HDTBootImageContent.ps1
|
function Add-HDTBootImageContent { <# .SYNOPSIS Declares a folder or file to be copied into the boot image, leaving every other line of workspace.yaml byte-identical. .DESCRIPTION The command an administrator types to put something of their own inside the WinPE image, and the one anything with an Add Content button has to run - if the command cannot do it, the window cannot do it either. THIS IS ONE FEATURE, NOT FOUR. A BGInfo build, a VNC server, a vendor PowerShell module and a background image are all the same thing: a source on the share and a destination inside the image. Every one of them is this command, and adding a fifth needs no new code. COPYING IS HALF OF IT. Nothing in WinPE starts what lands in the image; startnet.cmd runs wpeinit and then the deployment. Run Add-HDTBootImageStartCommand for the other half, or the tool is in the image and nobody ever sees it. THE DESTINATION IS A PATH INSIDE THE IMAGE, rooted at the image and not at the machine building it, which is why it must begin with a separator. A '..' in it is refused HERE rather than fifteen minutes into a build with a WIM mounted - the destination is resolved against the mount folder, so one that climbs out of it writes onto the build host's own disk. THE SOURCE IS RELATIVE TO THE WORKSPACE ROOT unless it is rooted, so content committed alongside the share travels with it. IT SPLICES LINES AND NEVER PARSES AND RE-EMITS. workspace.yaml is created with a comment header explaining deployRoot and the engine defaults, and an administrator adds their own notes from there on; a parser yields a dictionary and a dictionary has no comments in it. Only the lines this entry occupies are new. IT BUILDS THE bootImage BLOCK WHEN THERE IS NONE, which is the usual case: New-HDTWorkspace deliberately writes no boot image settings at all. IT RETURNS LINES AND WRITES NOTHING. Save-HDTWorkspaceDocument is what touches the share, so an edit can be composed, reviewed and abandoned without a file ever changing. .PARAMETER Line The document, already split into lines. .PARAMETER Source What to copy, relative to the workspace root unless it is rooted. .PARAMETER Destination Where it lands inside the image - a path rooted at the image, for example \HDT\Tools\BGInfo. .INPUTS None. This command does not accept pipeline input. .OUTPUTS System.String[] - the document with the entry added. .EXAMPLE Add-HDTBootImageContent -Line $line -Source 'Tools\BGInfo' -Destination '\HDT\Tools\BGInfo' .EXAMPLE $line = [System.IO.File]::ReadAllText($path) -split "`r?`n" $line = Add-HDTBootImageContent -Line $line -Source 'Tools\VNC' -Destination '\HDT\Tools\VNC' $line = Add-HDTBootImageStartCommand -Line $line -Command 'X:\HDT\Tools\VNC\winvnc.exe -service' Save-HDTWorkspaceDocument -Path $path -Line $line Both halves: the server is copied in, and something starts it. .LINK Add-HDTBootImageStartCommand .LINK Save-HDTWorkspaceDocument #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')] [OutputType([string[]])] param( [Parameter(Mandatory = $true, Position = 0)] [AllowEmptyCollection()] [AllowEmptyString()] [string[]] $Line, [Parameter(Mandatory = $true, Position = 1)] [ValidateNotNullOrEmpty()] [string] $Source, [Parameter(Mandatory = $true, Position = 2)] [ValidateNotNullOrEmpty()] [string] $Destination ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # -- what is being asked for --------------------------------------------- if ([string]::IsNullOrWhiteSpace($Source)) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $Source ` -Message 'a content entry names what to copy. Give a path relative to the workspace root, such as Tools\BGInfo.')) } if (-not $Destination.StartsWith('\')) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $Destination ` -Message ("the destination '{0}' does not start with a separator, and a destination is a path inside the image - it is rooted at the image, not at the machine building it. Write it as \HDT\Tools\BGInfo." -f $Destination))) } if ($Destination -like '*..*') { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $Destination ` -Message ("the destination '{0}' contains '..', which escapes the image and writes onto the machine building it. Name a path under the image root." -f $Destination))) } # -- what is already there ------------------------------------------------ $workspace = ConvertFrom-HDTWorkspaceLine -Line $Line foreach ($entry in @($workspace.BootImage.ExtraContent)) { if (([string] $entry.Source) -eq $Source -and ([string] $entry.Destination) -eq $Destination) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $Destination ` -Message ("this document already copies '{0}' to '{1}'. Copying the same content twice is not a thing to ask for by accident; a second source landing on the same destination is allowed, and merges into it." -f $Source, $Destination))) } } if (-not $PSCmdlet.ShouldProcess($Destination, ("Copy '{0}' into the boot image" -f $Source))) { return [string[]] @($Line) } # -- the entry ------------------------------------------------------------ $text = [string[]] @( ('- source: {0}' -f (ConvertTo-HDTRuleScalarText -Value $Source)) (' destination: {0}' -f (ConvertTo-HDTRuleScalarText -Value $Destination)) ) $block = Get-HDTWorkspaceKey -Line $Line -Path @('bootImage', 'extraContent') if ($null -ne $block) { $result = [string[]] @(Add-HDTWorkspaceItem -Line $Line -Block $block -Text $text) } else { # THE KEY, THE BLOCK ABOVE IT, OR BOTH. A new sequence is written with # its entries indented under the key, which is the shape every # hand-written sample in this repository uses. $written = New-Object -TypeName System.Collections.ArrayList [void] $written.Add('extraContent:') foreach ($current in $text) { [void] $written.Add(' ' + $current) } $result = [string[]] @(Set-HDTWorkspaceKey -Line $Line -Path @('bootImage', 'extraContent') ` -Text ([string[]] @($written))) } try { [void] (ConvertFrom-HDTWorkspaceLine -Line $result) } catch { $PSCmdlet.ThrowTerminatingError($_) } return [string[]] $result } |