UI/Console/HDTBootImage.xaml
|
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Windows PE" Height="760" Width="1180" MinHeight="520" MinWidth="880" WindowStartupLocation="Manual" Background="{DynamicResource HDTWindowBrush}"> <!-- THE WINDOWS PE WINDOW - Deployment Workbench's deployment share Properties, Windows PE tab, which is where an MDT admin has configured a boot image for fifteen years. Four tabs, and each one is the face of a command family that already exists: General Set-HDTWorkspaceProperty, Set-HDTBootImageUnattend, New-HDTBootImageUnattend, Set-HDTBootImageBackground, Set-HDTBootImageTimeZone, Get-HDTTimeZone, Add/Remove-HDTBootImageCertificate, Set-HDTBootImageClientCertificate, Set-HDTBootImageCertificatePassword Features Get-HDTAdkComponent, Add/Remove-HDTBootImageComponent Drivers Get-HDTDriverGroup, Set-HDTBootImageDriver Customisations Add/Remove-HDTBootImageContent, Add/Remove/Move-HDTBootImageStartCommand THE ENTRY COMMAND IS NOT ON THIS WINDOW. workspace.yaml carries one - the last line of startnet.cmd, which replaces the deployment payload for standalone media or a diagnostic image - and Set-HDTWorkspaceProperty sets it. It is off the General tab on purpose: the thing an administrator actually wants to run at boot is a start command, which the Customisations tab already holds, and a box next to it that also looks like "the command that runs" is how somebody puts run.cmd in the one place that stops the deployment ever starting. NOTHING ON THIS WINDOW EXISTS WITHOUT A COMMAND BEHIND IT, and that is the rule the console is built to: "if we don't have that command we can't do it using the WPF". Two things an MDT admin might look for are therefore ABSENT rather than drawn dead: - THE WELCOME-SCREEN SKIPS. workspace.yaml carries bootImage.skip (welcome, staticIp, deployRoot, credential) and there is no setter for it - Set-HDTWorkspaceProperty has no -Skip parameter. Four checkboxes here would be four controls with nothing to call. (Reordering start commands WAS one of them: Add took -First or appended and nothing moved a row, so the arrows could not exist. That is what Move-HDTBootImageStartCommand was written for - the window did not get the buttons until the command was there to call.) THE ARCHITECTURE BOX IS ON THIS WINDOW AND THE ADK LIST FOLLOWS IT. Get-HDTAdkComponent is asked per architecture, so amd64 and arm64 do not offer the same cabs - reading the box rather than assuming amd64 is what stops the Features tab describing an image nobody is building. UPDATE IS NOT SAVE, AND THEY ARE TWO BUTTONS. Save writes workspace.yaml - instant, and what every tab on this window edits. Update runs Update-HDTBootImage, which mounts, injects and rebuilds a WIM and an ISO and takes minutes. MDT splits them the same way: the Properties dialog has OK/Apply, and "Update Deployment Share" is its own action. One button doing both would mean an administrator who fixed a typo in the image name waits two and a half minutes for it. NO x:Class AND NO CODE-BEHIND, as HDTConsole.xaml and HDTSequenceEditor.xaml. Handlers attach in PowerShell, by name. NAMED CONTROLS ARE THE CONTRACT with New-HDTConsoleHost: HDTBootImageTitleText, HDTBootImagePathText, HDTBootImageNameBox, HDTBootImageArchitectureBox, HDTBootImageLanguageBox, HDTBootImageScratchBox, HDTBootImageUnattendBox, HDTBootImageUnattendBrowseButton, HDTBootImageUnattendTemplateButton, HDTBootImageUnattendOpenButton, HDTBootImageBackgroundBox, HDTBootImageBackgroundBrowseButton, HDTBootImageTimeZoneBox, HDTBootImageTimeZoneHintText, HDTComponentList, HDTComponentSizeText, HDTCertificateList, HDTCertificateBox, HDTCertificateBrowseButton, HDTCertificateAddButton, HDTCertificateRemoveButton, HDTCertificateSummaryText, HDTClientCertificateBox, HDTClientCertificateBrowseButton, HDTClientCertificatePasswordButton, HDTClientCertificateWarningText, HDTDriverGroupBox, HDTContentList, HDTContentSourceBox, HDTContentBrowseButton, HDTContentDestinationBox, HDTContentAddButton, HDTContentRemoveButton, HDTStartCommandList, HDTStartCommandBox, HDTStartCommandFirstCheck, HDTStartCommandAddButton, HDTStartCommandRemoveButton, HDTStartCommandUpButton, HDTStartCommandDownButton, HDTBootImageCommandText, HDTBootImageUpdateButton, HDTBootImageSaveButton and HDTBootImageCloseButton. THE PALETTE IS THE CONSOLE'S - every colour a DynamicResource under the keys HDTConsole.xaml declares, so Get-HDTConsoleTheme repaints this window with the others. --> <Window.Resources> <SolidColorBrush x:Key="HDTWindowBrush" Color="#FFF3F3F3" /> <SolidColorBrush x:Key="HDTBannerBrush" Color="#FF0E639C" /> <SolidColorBrush x:Key="HDTBannerTextBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="HDTBannerLabelBrush" Color="#FFD6E9F5" /> <SolidColorBrush x:Key="HDTPanelBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="HDTPanelTextBrush" Color="#FF1B1B1B" /> <SolidColorBrush x:Key="HDTBorderBrush" Color="#FFC8C8C8" /> <SolidColorBrush x:Key="HDTLabelBrush" Color="#FF0E639C" /> <SolidColorBrush x:Key="HDTHintBrush" Color="#FF6A6A6A" /> <SolidColorBrush x:Key="HDTCommandBrush" Color="#FFF7F7F7" /> <SolidColorBrush x:Key="HDTCommandTextBrush" Color="#FF0A3069" /> <SolidColorBrush x:Key="HDTFieldBrush" Color="#FFEDEDED" /> <SolidColorBrush x:Key="HDTFooterBrush" Color="#FFE8E8E8" /> <SolidColorBrush x:Key="HDTButtonBrush" Color="#FF0E639C" /> <SolidColorBrush x:Key="HDTButtonTextBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="HDTButtonHoverBrush" Color="#FFCCE4F7" /> <SolidColorBrush x:Key="HDTButtonHoverTextBrush" Color="#FF000000" /> <SolidColorBrush x:Key="HDTButtonPressedBrush" Color="#FFA9CFEC" /> <!-- The console's button, template and all. See HDTConsole.xaml for why the colours are Setters inside a ControlTemplate rather than local values on the element. --> <!-- THE ? THIS PRODUCT ALREADY HAS, copied verbatim from HDTNewWorkspace.xaml, which shares it with HDTNewSequence, HDTImportApplication, HDTApplicationDetection, HDTConsole and HDTSequenceEditor. A 22px outlined dot with a bold glyph and a help cursor - not a solid button, which is what this tab first got and what made it read as a different kind of control. --> <Style TargetType="Border" x:Key="HDTHelpDot"> <Setter Property="Background" Value="Transparent" /> <Setter Property="Width" Value="22" /> <Setter Property="Height" Value="22" /> <Setter Property="CornerRadius" Value="11" /> <Setter Property="BorderBrush" Value="{DynamicResource HDTBorderBrush}" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="HorizontalAlignment" Value="Right" /> <Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="Cursor" Value="Help" /> <Setter Property="Margin" Value="8,0,0,10" /> <Setter Property="ToolTipService.ShowDuration" Value="60000" /> <Setter Property="ToolTipService.InitialShowDelay" Value="200" /> </Style> <Style TargetType="TextBlock" x:Key="HDTHelpGlyph"> <Setter Property="Text" Value="?" /> <Setter Property="Foreground" Value="{DynamicResource HDTHintTextBrush}" /> <Setter Property="FontSize" Value="12" /> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" /> </Style> <Style TargetType="Button"> <Setter Property="Background" Value="{DynamicResource HDTButtonBrush}" /> <Setter Property="Foreground" Value="{DynamicResource HDTButtonTextBrush}" /> <Setter Property="BorderThickness" Value="0" /> <Setter Property="FontSize" Value="13" /> <Setter Property="Padding" Value="14,6,14,6" /> <Setter Property="Margin" Value="0,0,8,0" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border x:Name="HDTButtonSurface" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="{TemplateBinding Padding}" /> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="HDTButtonSurface" Property="Background" Value="{DynamicResource HDTButtonHoverBrush}" /> <Setter Property="Foreground" Value="{DynamicResource HDTButtonHoverTextBrush}" /> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter TargetName="HDTButtonSurface" Property="Background" Value="{DynamicResource HDTButtonPressedBrush}" /> <Setter Property="Foreground" Value="{DynamicResource HDTButtonHoverTextBrush}" /> </Trigger> <!-- A disabled action must LOOK disabled. Remove means nothing until a row is selected, and a bright button that does nothing when pressed is how an administrator concludes the window is broken. --> <Trigger Property="IsEnabled" Value="False"> <Setter TargetName="HDTButtonSurface" Property="Background" Value="{DynamicResource HDTFooterBrush}" /> <Setter Property="Foreground" Value="{DynamicResource HDTBorderBrush}" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="TextBlock"> <Setter Property="Foreground" Value="{DynamicResource HDTPanelTextBrush}" /> <Setter Property="FontFamily" Value="Segoe UI" /> <Setter Property="FontSize" Value="13" /> </Style> <!-- The hint under a field: the same size, deliberately quieter. It says what an EMPTY box means, which is the question every one of these fields raises and none of them can answer by itself. --> <Style x:Key="HDTHintText" TargetType="TextBlock"> <Setter Property="Foreground" Value="{DynamicResource HDTHintBrush}" /> <Setter Property="FontFamily" Value="Segoe UI" /> <Setter Property="FontSize" Value="12" /> <Setter Property="TextWrapping" Value="Wrap" /> </Style> <!-- WHITE, BECAUSE THESE ARE TYPED INTO. HDTFieldBrush is the read-only wash the console's detail pane puts on rows that are a reading rather than a setting; every box on this window is a setting. It wore the wash for months and nobody could tell, because the wash was #FAFAFA and invisible against white - the moment it became a grey somebody could see, this window started claiming its image name could not be changed. --> <Style TargetType="TextBox"> <Setter Property="Background" Value="{DynamicResource HDTPanelBrush}" /> <Setter Property="Foreground" Value="{DynamicResource HDTPanelTextBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource HDTBorderBrush}" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="FontFamily" Value="Segoe UI" /> <Setter Property="FontSize" Value="13" /> <Setter Property="Padding" Value="6,4,6,4" /> </Style> <Style TargetType="ComboBox"> <Setter Property="FontFamily" Value="Segoe UI" /> <!-- AND IT MUST NOT LOOK READ-ONLY. WPF's default ComboBox template paints its own grey chrome and ignores Background, so a dropdown beside white boxes reads as a disabled one. IsEditable puts a real text box in the closed control, which takes the Background; IsReadOnly keeps it from being typed into. No ComboBox here uses an ItemTemplate, so what it shows is what it showed before. --> <Setter Property="IsEditable" Value="True" /> <Setter Property="IsReadOnly" Value="True" /> <Setter Property="Background" Value="{DynamicResource HDTPanelBrush}" /> <Setter Property="Foreground" Value="{DynamicResource HDTPanelTextBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource HDTBorderBrush}" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="FontSize" Value="13" /> <Setter Property="Padding" Value="6,4,6,4" /> </Style> <Style TargetType="CheckBox"> <Setter Property="Foreground" Value="{DynamicResource HDTPanelTextBrush}" /> <Setter Property="FontFamily" Value="Segoe UI" /> <Setter Property="FontSize" Value="13" /> </Style> <Style TargetType="TabItem"> <Setter Property="Foreground" Value="{DynamicResource HDTPanelTextBrush}" /> <Setter Property="FontFamily" Value="Segoe UI" /> <Setter Property="FontSize" Value="12" /> <Setter Property="Padding" Value="14,5,14,5" /> </Style> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <!-- the banner: which image, and which workspace it is configured in --> <Border Grid.Row="0" Background="{DynamicResource HDTBannerBrush}" Padding="24,14,24,14"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock Grid.Row="0" x:Name="HDTBootImageTitleText" Foreground="{DynamicResource HDTBannerTextBrush}" FontFamily="Segoe UI" FontSize="19" /> <TextBlock Grid.Row="1" x:Name="HDTBootImagePathText" Text="" Foreground="{DynamicResource HDTBannerLabelBrush}" FontFamily="Consolas, Courier New" FontSize="12" Margin="0,4,0,0" /> </Grid> </Border> <TabControl Grid.Row="1" Margin="24,18,24,0" Background="{DynamicResource HDTPanelBrush}" BorderBrush="{DynamicResource HDTBorderBrush}"> <!-- ==================== General ==================== --> <TabItem x:Name="HDTBootImageGeneral" > <ScrollViewer VerticalScrollBarVisibility="Auto" Padding="18,16,18,16"> <!-- THE LABELS ARE TOP-ALIGNED, NOT CENTRED, and the 6px is the box's own border and padding. Each field cell holds a box AND the hint under it, so a centred label centres against both and lands below the box it names - which reads as a row that failed to line up rather than as a label with an explanation beneath it. --> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="200" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock x:Name="HDTBootImageImageNameLabel" Grid.Row="0" Grid.Column="0" VerticalAlignment="Top" Margin="0,6,0,12" /> <StackPanel Grid.Row="0" Grid.Column="1" Margin="0,0,0,12"> <TextBox x:Name="HDTBootImageNameBox" Width="360" HorizontalAlignment="Left" /> <TextBlock x:Name="HDTBootImageImageNameHint" Style="{StaticResource HDTHintText}" Margin="0,5,0,0" MaxWidth="560" HorizontalAlignment="Left" /> </StackPanel> <TextBlock x:Name="HDTBootImageArchitectureLabel" Grid.Row="1" Grid.Column="0" VerticalAlignment="Top" Margin="0,6,0,12" /> <StackPanel Grid.Row="1" Grid.Column="1" Margin="0,0,0,12"> <ComboBox x:Name="HDTBootImageArchitectureBox" Width="180" HorizontalAlignment="Left" SelectedValuePath="Tag"> <ComboBoxItem Tag="amd64" Content="amd64" /> <ComboBoxItem Tag="arm64" Content="arm64" /> </ComboBox> <TextBlock x:Name="HDTBootImageArchitectureHint" Style="{StaticResource HDTHintText}" Margin="0,5,0,0" MaxWidth="560" HorizontalAlignment="Left" /> </StackPanel> <TextBlock x:Name="HDTBootImageLanguageLabel" Grid.Row="2" Grid.Column="0" VerticalAlignment="Top" Margin="0,6,0,12" /> <StackPanel Grid.Row="2" Grid.Column="1" Margin="0,0,0,12"> <TextBox x:Name="HDTBootImageLanguageBox" Width="180" HorizontalAlignment="Left" /> <TextBlock x:Name="HDTBootImageLanguageHint" Style="{StaticResource HDTHintText}" Margin="0,5,0,0" MaxWidth="560" HorizontalAlignment="Left" /> </StackPanel> <TextBlock x:Name="HDTBootImageScratchSpaceLabel" Grid.Row="3" Grid.Column="0" VerticalAlignment="Top" Margin="0,6,0,12" /> <StackPanel Grid.Row="3" Grid.Column="1" Margin="0,0,0,12"> <ComboBox x:Name="HDTBootImageScratchBox" Width="180" HorizontalAlignment="Left" SelectedValuePath="Tag"> <ComboBoxItem Tag="32" Content="32 MB" /> <ComboBoxItem Tag="64" Content="64 MB" /> <ComboBoxItem Tag="128" Content="128 MB" /> <ComboBoxItem Tag="256" Content="256 MB" /> <ComboBoxItem Tag="512" Content="512 MB" /> <ComboBoxItem Tag="1024" Content="1024 MB" /> </ComboBox> <TextBlock x:Name="HDTBootImageScratchSpaceHint" Style="{StaticResource HDTHintText}" Margin="0,5,0,0" MaxWidth="560" HorizontalAlignment="Left" /> </StackPanel> <!-- THE ANSWER FILE, AND IT IS wpeinit's ARGUMENT rather than a line of its own. wpeinit processes it - Display, EnableFirewall, EnableNetwork, LogPath, PageFile, Restart, RunSynchronous, RunAsynchronous - so the WinPE firewall is set HERE and not by a tool in a start command. `wpeutil disablefirewall` is a command for a technician at a prompt; this is how an IMAGE is built with the setting. --> <TextBlock x:Name="HDTBootImageAnswerFileLabel" Grid.Row="4" Grid.Column="0" VerticalAlignment="Top" Margin="0,6,0,12" /> <StackPanel Grid.Row="4" Grid.Column="1" Margin="0,0,0,12"> <StackPanel Orientation="Horizontal"> <TextBox x:Name="HDTBootImageUnattendBox" Width="470" HorizontalAlignment="Left" FontFamily="Consolas, Courier New" Margin="0,0,8,0" /> <Button x:Name="HDTBootImageUnattendBrowseButton" MinWidth="80" /> </StackPanel> <!-- BROWSE ALONE ASSUMES THE FILE EXISTS, and on a new share it does not: a windowsPE answer file is a document written from Microsoft's schema, and "go and write one" is not an answer a properties page should give. USE TEMPLATE writes the one this module ships onto the share and names it here. OPEN is what makes that worth doing - wpeinit also reads EnableFirewall, EnableNetwork, LogPath, PageFile and Restart, and every one of them is set by editing this file. There is no page for those and there should not be: they are wpeinit's list, not HDT's, and it changes with WinPE. --> <StackPanel Orientation="Horizontal" Margin="0,8,0,0"> <Button x:Name="HDTBootImageUnattendTemplateButton" MinWidth="110" Margin="0,0,8,0" /> <Button x:Name="HDTBootImageUnattendOpenButton" MinWidth="80" /> </StackPanel> <TextBlock x:Name="HDTBootImageAnswerFileHint" Style="{StaticResource HDTHintText}" Margin="0,5,0,0" MaxWidth="560" HorizontalAlignment="Left" /> </StackPanel> <!-- MDT'S "custom background bitmap file". DESIGN 11.1 says HDT does not NEED one - the progress window paints its own full-screen ground, which is what the wallpaper is for in MDT - but the boot image still has a desktop behind that window, and branding it should not mean hand-editing YAML. THE FILE PICKER FILTERS TO JPEG because WinPE reads \Windows\System32\winpe.jpg and nothing else. A .png chosen here would be copied into the image and never shown; Set-HDTBootImageBackground refuses one, and the dialog tries not to offer it in the first place. --> <!-- MDT'S LABEL, MINUS THE WORD THAT IS NO LONGER TRUE. Deployment Workbench calls this "Custom background bitmap file" and an MDT admin is looking for that phrase - but WinPE 10 and 11 read \Windows\System32\winpe.jpg, so "bitmap" would be the one word on the row that sends somebody looking for a .bmp. The rest of the phrase is MDT's, and the hint says which format it has to be. --> <TextBlock x:Name="HDTBootImageCustomBackgroundFileLabel" Grid.Row="5" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Top" Margin="0,6,8,12" /> <StackPanel Grid.Row="5" Grid.Column="1" Margin="0,0,0,12"> <StackPanel Orientation="Horizontal"> <TextBox x:Name="HDTBootImageBackgroundBox" Width="470" HorizontalAlignment="Left" FontFamily="Consolas, Courier New" Margin="0,0,8,0" /> <Button x:Name="HDTBootImageBackgroundBrowseButton" MinWidth="80" /> </StackPanel> <TextBlock x:Name="HDTBootImageCustomBackgroundFileHint" Style="{StaticResource HDTHintText}" Margin="0,5,0,0" MaxWidth="560" HorizontalAlignment="Left" /> </StackPanel> <!-- THE TIME ZONE, WHICH THE ANSWER FILE ABOVE CANNOT SET. WinPE's windowsPE pass carries locale and nothing else - Microsoft's TimeZone belongs to Shell-Setup, which only runs in the deployed OS's passes - so this row is what puts tzutil into startnet.cmd, and what the deployed machine's unattend inherits. A LIST, BECAUSE tzutil TAKES AN ID. Nobody knows the ids; everybody recognises "(UTC+02:00) Jerusalem". --> <TextBlock x:Name="HDTBootImageTimeZoneLabel" Grid.Row="6" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Top" Margin="0,6,8,12" /> <StackPanel Grid.Row="6" Grid.Column="1" Margin="0,0,0,12"> <ComboBox x:Name="HDTBootImageTimeZoneBox" Width="470" HorizontalAlignment="Left" DisplayMemberPath="Display" SelectedValuePath="Id" /> <TextBlock x:Name="HDTBootImageTimeZoneHintText" Style="{StaticResource HDTHintText}" Margin="0,5,0,0" MaxWidth="620" HorizontalAlignment="Left" Text="" /> </StackPanel> <!-- "PRESS ANY KEY TO BOOT FROM CD OR DVD" IS A BOOT SECTOR, NOT A SETTING WinPE READS. oscdimg writes efisys.bin for the prompt and efisys_noprompt.bin for silence, and HDT has always written the silent one - a machine nobody is standing at cannot answer a keypress, and an unattended deployment that waited for one would sit at a black screen until somebody walked past. UNTICKED IS THEREFORE THE DEFAULT and matches every image built before this box existed. Ticking it is for the machine whose boot order still has the DVD first: the prompt is what lets it fall through to the disk instead of deploying itself again. UEFI ONLY. The BIOS boot sector has no quiet variant, so a BIOS ISO prompts either way. --> <TextBlock x:Name="HDTBootImageBootPromptLabel" Grid.Row="9" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Top" Margin="0,6,8,12" /> <StackPanel Grid.Row="9" Grid.Column="1" Margin="0,0,0,12"> <CheckBox x:Name="HDTBootImagePromptForKeyCheck" HorizontalAlignment="Left" /> <TextBlock x:Name="HDTBootImageBootPromptHint" Style="{StaticResource HDTHintText}" Margin="0,5,0,0" MaxWidth="620" HorizontalAlignment="Left" /> </StackPanel> <!-- THE CERTIFICATES, AS TWO ROWS ON THIS TAB rather than a tab of their own. They are two paths and a password - the same shape as the answer file and the background directly above them - and a tab for two rows makes an administrator hunt for a setting that belongs beside the others. THE FIRST ROW IS A LIST because a PKI is a chain: a root and the subordinate that actually issued the certificate, and an image trusting only the root still cannot validate what the issuing CA signed. IT WAS A DROP-DOWN AND THAT WAS WRONG. One box did two jobs - the thing being typed and the things already trusted - so adding one emptied the box and hid the result behind a click: the only visible change was a grey count line, and it read as nothing having happened. A list shows what the image trusts without being asked, which is what this row is for. It is the shape the Customisations tab uses for extra content, and it costs the vertical space a tab that already scrolls can afford. --> <TextBlock x:Name="HDTBootImageCertificateAuthoritiesLabel" Grid.Row="7" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Top" Margin="0,6,8,12" /> <StackPanel Grid.Row="7" Grid.Column="1" Margin="0,0,0,12"> <Border Background="{DynamicResource HDTPanelBrush}" BorderBrush="{DynamicResource HDTBorderBrush}" BorderThickness="1" Width="470" HorizontalAlignment="Left" Height="86"> <ListBox x:Name="HDTCertificateList" BorderThickness="0" Background="Transparent" DisplayMemberPath="Path" FontFamily="Consolas, Courier New" FontSize="12" /> </Border> <StackPanel Orientation="Horizontal" Margin="0,8,0,0"> <TextBox x:Name="HDTCertificateBox" Width="470" HorizontalAlignment="Left" FontFamily="Consolas, Courier New" Margin="0,0,8,0" /> <Button x:Name="HDTCertificateBrowseButton" MinWidth="80" /> <Button x:Name="HDTCertificateAddButton" MinWidth="70" /> <Button x:Name="HDTCertificateRemoveButton" MinWidth="80" Margin="0" /> </StackPanel> <TextBlock x:Name="HDTCertificateSummaryText" Style="{StaticResource HDTHintText}" Margin="0,5,0,0" MaxWidth="620" HorizontalAlignment="Left" Text="" /> <!-- ONE LINE, BECAUSE THIS TAB IS ALREADY LONG. The rest of the reasoning is in the schema and in Add-HDTBootImageCertificate's help; what has to be on the window is the store, which is what makes these two rows different from each other. --> <TextBlock x:Name="HDTBootImageCertificateAuthoritiesHint" Style="{StaticResource HDTHintText}" Margin="0,3,0,0" MaxWidth="620" HorizontalAlignment="Left" /> </StackPanel> <!-- THE SECOND ROW IS ONE FILE AND ONE PASSWORD, and the password is the only value on this window that goes to a different file: Control\certificate-password.json, never workspace.yaml, which is the document an administrator commits. --> <TextBlock x:Name="HDTBootImageMachineCertificateLabel" Grid.Row="8" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Top" Margin="0,6,8,12" /> <StackPanel Grid.Row="8" Grid.Column="1" Margin="0,0,0,12"> <StackPanel Orientation="Horizontal"> <TextBox x:Name="HDTClientCertificateBox" Width="470" HorizontalAlignment="Left" FontFamily="Consolas, Courier New" Margin="0,0,8,0" /> <Button x:Name="HDTClientCertificateBrowseButton" MinWidth="80" /> <Button x:Name="HDTClientCertificatePasswordButton" MinWidth="110" Margin="0" /> </StackPanel> <TextBlock x:Name="HDTClientCertificateWarningText" Text="" Foreground="{DynamicResource HDTErrorBrush}" FontSize="12" TextWrapping="Wrap" Margin="0,5,0,0" MaxWidth="620" HorizontalAlignment="Left" /> <TextBlock x:Name="HDTBootImageMachineCertificateHint" Style="{StaticResource HDTHintText}" Margin="0,3,0,0" MaxWidth="620" HorizontalAlignment="Left" /> </StackPanel> <!-- THERE IS NO APPLY ON THIS TAB, and that is not an omission. Every tab edits the same in-memory document; Save at the bottom is what writes it, and it is where MDT's properties dialog puts Apply too. A second Apply mid-form would have to mean something different from Save to be worth pressing, and it does not - it sat under the last field and read as "apply the answer file". --> <!-- WHAT THIS TAB DOES NOT SHOW, said here rather than drawn as dead controls: workspace.yaml also carries bootImage.skip - the four Welcome-screen Skip* properties - and no command sets them, so there is nothing for a checkbox to call. They are edited in the file until there is one. --> <TextBlock x:Name="HDTBootImageMachineCertificateHint2" Grid.Row="10" Grid.Column="1" Style="{StaticResource HDTHintText}" Margin="0,22,0,0" MaxWidth="560" HorizontalAlignment="Left" /> </Grid> </ScrollViewer> </TabItem> <!-- ==================== Features ==================== --> <TabItem x:Name="HDTBootImageFeatures" > <Grid Margin="18,16,18,16"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock x:Name="HDTBootImageMachineCertificateHint3" Grid.Row="0" Style="{StaticResource HDTHintText}" Margin="0,0,0,12" MaxWidth="820" HorizontalAlignment="Left" /> <!-- A TICK PER COMPONENT, AND SIX OF THEM CANNOT BE UNTICKED. Get-HDTAdkComponent marks the ones HDT applies to every image - WinPE-NetFx and WinPE-PowerShell among them, because a boot image without them cannot run the engine at all. They are shown ticked and disabled rather than hidden: an administrator looking for PowerShell in this list has to find it and see that it is already there. REQUIRES IS ON THE ROW, not in a dialog after the fact. WinPE-PowerShell needs WinPE-WMI beside it, and a list that only says so once the build fails has spent two and a half minutes teaching it. --> <!-- ROW 1, THE ONE THAT STRETCHES. This Border and the total below it were both in row 2, so the total painted over the first component in the list, and row 1 sat empty taking all the height. --> <Border Grid.Row="1" Background="{DynamicResource HDTPanelBrush}" BorderBrush="{DynamicResource HDTBorderBrush}" BorderThickness="1"> <ScrollViewer VerticalScrollBarVisibility="Auto" Padding="12,10,12,10"> <ItemsControl x:Name="HDTComponentList"> <ItemsControl.ItemTemplate> <DataTemplate> <Grid Margin="0,3,0,3"> <Grid.ColumnDefinitions> <ColumnDefinition Width="250" /> <ColumnDefinition Width="80" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <CheckBox Grid.Column="0" IsChecked="{Binding Declared, Mode=TwoWay}" IsEnabled="{Binding CanChange}" Content="{Binding Name}" /> <TextBlock Grid.Column="1" Text="{Binding SizeText}" Foreground="{DynamicResource HDTHintBrush}" FontFamily="Consolas, Courier New" FontSize="12" HorizontalAlignment="Right" Margin="0,0,16,0" /> <!-- WHAT IT DOES, THEN WHAT COMES WITH IT. The ADK ships no descriptions at all - the cab names are the only metadata there is - so this text is a table HDT carries, from Microsoft's OC reference, the same way MDT carries its own. Without it the row read 'WinPE-Dot3Svc 1.3 MB'. --> <TextBlock Grid.Column="2" Text="{Binding DetailText}" Foreground="{DynamicResource HDTHintBrush}" FontSize="12" TextTrimming="CharacterEllipsis" ToolTip="{Binding DetailText}" /> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </ScrollViewer> </Border> <!-- THE TOTAL, BECAUSE THE COST OF THIS TAB IS THE THING IT HIDES. Every tick is megabytes in a WIM that has to be transferred to every machine that PXE boots, and the list gives no sense of that one row at a time. --> <TextBlock Grid.Row="2" x:Name="HDTComponentSizeText" Text="" Style="{StaticResource HDTHintText}" Margin="0,12,0,0" /> </Grid> </TabItem> <!-- ==================== Drivers ==================== --> <TabItem x:Name="HDTBootImageDrivers" > <ScrollViewer VerticalScrollBarVisibility="Auto" Padding="18,16,18,16"> <StackPanel> <TextBlock x:Name="HDTBootImageMachineCertificateHint4" Style="{StaticResource HDTHintText}" Margin="0,0,0,14" MaxWidth="760" HorizontalAlignment="Left" /> <!-- A LIST, NOT A BOX YOU TYPE INTO. A group is a folder under Drivers\ on the share, so the legal answers are knowable - Get-HDTDriverGroup reads them - and a typed one that is wrong builds an image with NO drivers in it, warned about at build time and discovered on a bench with a disk WinPE cannot see. "No drivers" IS THE FIRST ROW rather than an empty selection: it is a real choice, and a list you can only say it in by clearing the box is a list you cannot say it in. --> <TextBlock x:Name="HDTBootImageDriverGroupLabel" Margin="0,0,0,6" /> <ComboBox x:Name="HDTDriverGroupBox" Width="420" HorizontalAlignment="Left" DisplayMemberPath="Display" SelectedValuePath="Name" /> <!-- NO APPLY AND NO CLEAR HERE EITHER. Save writes what is in the box, and EMPTYING IT IS CLEARING IT - a Clear button beside a box you can already empty is a second way to do one thing, and the two disagree the first time somebody empties the box and presses Save expecting Clear's behaviour. Set-HDTBootImageDriver still has -Clear; it is what Save runs on an empty box. --> <TextBlock x:Name="HDTBootImageDriverGroupHint" Style="{StaticResource HDTHintText}" Margin="0,6,0,0" MaxWidth="620" HorizontalAlignment="Left" /> </StackPanel> </ScrollViewer> </TabItem> <!-- ==================== Customisations ==================== --> <TabItem x:Name="HDTBootImageCustomisations" > <Grid Margin="18,16,18,16"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <!-- TWO HALVES, AND THEY ONLY WORK TOGETHER. Copying BGInfo or a VNC server into the image starts nothing - the entry command is the deployment, and it does not return. The top half puts a file in the image; the bottom half is what runs it. A window with only the first would let an administrator ship a tool that never launches, which is the failure this pair exists to stop. --> <TextBlock x:Name="HDTBootImageExtraContentLabel" Grid.Row="0" FontSize="14" Margin="0,0,0,8" /> <Border Grid.Row="1" Background="{DynamicResource HDTPanelBrush}" BorderBrush="{DynamicResource HDTBorderBrush}" BorderThickness="1" MinHeight="110"> <ListBox x:Name="HDTContentList" BorderThickness="0" Background="Transparent" FontSize="13"> <ListBox.ItemTemplate> <DataTemplate> <Grid Margin="0,2,0,2"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="260" /> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding Source}" FontFamily="Consolas, Courier New" FontSize="12" TextTrimming="CharacterEllipsis" /> <TextBlock Grid.Column="1" Text=" → " Foreground="{DynamicResource HDTHintBrush}" FontSize="12" /> <TextBlock Grid.Column="2" Text="{Binding Destination}" FontFamily="Consolas, Courier New" FontSize="12" TextTrimming="CharacterEllipsis" /> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Border> <!-- THE TWO BOXES ARE LABELLED, and on this row it is not decoration: they hold a path on the build machine and a path inside the image, they look identical, and putting them the wrong way round produces an image that builds and is missing the tool. A tooltip is no help - it needs the pointer to already be in the right box. --> <Grid Grid.Row="2" Margin="0,10,0,0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="260" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock x:Name="HDTBootImageSourceOnThisMachine" Grid.Row="0" Grid.Column="0" Style="{StaticResource HDTHintText}" Margin="0,0,8,4" /> <TextBlock x:Name="HDTBootImageWhereItLandsInsideLabel" Grid.Row="0" Grid.Column="2" Style="{StaticResource HDTHintText}" Margin="0,0,8,4" /> <TextBox Grid.Row="1" Grid.Column="0" x:Name="HDTContentSourceBox" FontFamily="Consolas, Courier New" Margin="0,0,8,0" /> <Button Grid.Row="1" Grid.Column="1" x:Name="HDTContentBrowseButton" MinWidth="80" /> <TextBox Grid.Row="1" Grid.Column="2" x:Name="HDTContentDestinationBox" FontFamily="Consolas, Courier New" Margin="0,0,8,0" /> <StackPanel Grid.Row="1" Grid.Column="3" Orientation="Horizontal"> <Button x:Name="HDTContentAddButton" MinWidth="80" /> <Button x:Name="HDTContentRemoveButton" MinWidth="80" Margin="0" /> </StackPanel> </Grid> <StackPanel Grid.Row="3" Margin="0,24,0,8"> <TextBlock x:Name="HDTBootImageStartCommandsLabel" FontSize="14" Margin="0,0,0,4" /> <TextBlock x:Name="HDTBootImageStartCommandsHint" Style="{StaticResource HDTHintText}" MaxWidth="820" HorizontalAlignment="Left" /> </StackPanel> <!-- THE ORDER IS THE LIST'S ORDER, and startnet.cmd runs them in it - after wpeinit, so anything needing the network has one, and before the entry command, because that one never comes back. There are no up and down buttons: Add appends or takes -First, Remove takes a command, and no command moves one. Drawing arrows that call nothing would be the window promising what the module cannot do. --> <Border Grid.Row="4" Background="{DynamicResource HDTPanelBrush}" BorderBrush="{DynamicResource HDTBorderBrush}" BorderThickness="1" MinHeight="90"> <!-- IT SHOWS THE DOCUMENT, WHICH IS WHAT THIS WINDOW EDITS. startnet.cmd and workspace.yaml are two different files: the yaml is the authored one, and the .cmd is generated from it at build time, with `call` in front of a batch file so cmd.exe returns. An earlier version showed the generated line here. It put a row on screen that appears in no file this window can save, and made a list that is otherwise a faithful view of the yaml quietly not one. The `call` rule is stated in the hint above instead - words about the other file, rather than the other file's contents wearing this one's clothes. --> <ListBox x:Name="HDTStartCommandList" BorderThickness="0" Background="Transparent" DisplayMemberPath="Text" FontFamily="Consolas, Courier New" FontSize="12" /> </Border> <Grid Grid.Row="5" Margin="0,10,0,0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <TextBox Grid.Column="0" x:Name="HDTStartCommandBox" FontFamily="Consolas, Courier New" Margin="0,0,12,0" /> <CheckBox Grid.Column="1" x:Name="HDTStartCommandFirstCheck" VerticalAlignment="Center" Margin="0,0,12,0" /> <StackPanel Grid.Column="2" Orientation="Horizontal"> <!-- THE ARROWS ARE GLYPHS, AND THEY MOVE ONE PLACE. Order is semantics in this list - startnet.cmd runs the lines in it, synchronously - so a tool that must be up before the next line runs has to be above it. Move-HDTBootImageStartCommand is what they call; before it existed, reordering meant Remove and Add again. Segoe UI Symbol, NOT an image: WinPE has no icon pipeline and the console should not need one either, and a glyph scales with the window. --> <Button x:Name="HDTStartCommandUpButton" Content="" FontFamily="Segoe UI Symbol" FontSize="12" MinWidth="36" Margin="0,0,4,0" /> <Button x:Name="HDTStartCommandDownButton" Content="" FontFamily="Segoe UI Symbol" FontSize="12" MinWidth="36" Margin="0,0,12,0" /> <Button x:Name="HDTStartCommandAddButton" MinWidth="80" /> <Button x:Name="HDTStartCommandRemoveButton" MinWidth="80" Margin="0" /> </StackPanel> </Grid> </Grid> </TabItem> <!-- THE RULES TAB. MDT put CustomSettings.ini on the deployment share's Properties, as text you edit in place; HDT's equivalent is rules.yaml and it goes in the same place, because that is where an administrator arriving from MDT will look for it. TEXT, NOT A GRID, AND THAT IS THE POINT. Add-HDTRule and Set-HDTRule exist and a grid could be built on them - but the document is walked top to bottom with first match winning per variable, so the ORDER of the rules, and the comments explaining that order, ARE its meaning. A grid would hide the one thing the administrator is reasoning about. SAVE IS DARK UNTIL THE DOCUMENT PARSES. Assert-HDTRuleLine is the same gate Add-HDTRule passes through, so a file WinPE would refuse at three in the morning is refused here, at the desk. --> <TabItem x:Name="HDTBootImageRules" > <Grid Margin="18,16,18,16"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,8"> <TextBlock x:Name="HDTBootImageRulesLabel" FontSize="14" /> <TextBlock x:Name="HDTRulesSummaryText" Style="{StaticResource HDTHintText}" Margin="10,3,0,0" VerticalAlignment="Center" /> </StackPanel> <!-- THE VOCABULARY, WHERE THE VOCABULARY IS USED. A rules file is written in forty variable names, the MDT names they came from, and MDT's #Left(...)# expressions, and this window offered none of them - so the only way to find out what may be written was to read DESIGN.md on another machine, while this window is open over a share. RIGHT-ALIGNED IN THE SAME CELL as the label, rather than a new column: the label and its summary already own the left of that row and neither should move because a question mark was added to it. --> <Border x:Name="HDTRulesHelpButton" Grid.Row="0" Style="{StaticResource HDTHelpDot}" VerticalAlignment="Top"> <TextBlock Style="{StaticResource HDTHelpGlyph}" /> </Border> <Border Grid.Row="1" Background="{DynamicResource HDTPanelBrush}" BorderBrush="{DynamicResource HDTBorderBrush}" BorderThickness="1" MinHeight="220"> <!-- Consolas, because YAML is whitespace-significant and a proportional font makes a two-space indent impossible to see. --> <!-- WHITE, NOT THE FIELD GREY. Every other TextBox on this window is a one-line field and wears HDTFieldBrush; a panel-sized box in the same grey reads as output rather than as something you type into, and got reported as read-only when it never was. --> <TextBox x:Name="HDTRulesBox" BorderThickness="0" Background="{DynamicResource HDTPanelBrush}" FontFamily="Consolas" FontSize="13" AcceptsReturn="True" AcceptsTab="True" TextWrapping="NoWrap" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" VerticalContentAlignment="Top" /> </Border> <!-- The engine's own refusal, in the engine's own words, in front of the administrator BEFORE it can reach a booted machine. Empty and invisible while the document is good. --> <TextBlock x:Name="HDTRulesProblemText" Grid.Row="2" Margin="0,8,0,0" Foreground="{DynamicResource HDTCommandTextBrush}" TextWrapping="Wrap" Visibility="Collapsed" /> <StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0"> <TextBlock x:Name="HDTBootImageRulesHint" Style="{StaticResource HDTHintText}" MaxWidth="520" VerticalAlignment="Center" Margin="0,0,12,0" /> <Button x:Name="HDTRulesReloadButton" MinWidth="90" Margin="0,0,10,0" /> <Button x:Name="HDTRulesSaveButton" MinWidth="90" Margin="0" /> </StackPanel> </Grid> </TabItem> <!-- THE BOOTSTRAP TAB. MDT's Bootstrap.ini is what WinPE reads before it has a share: where the share is, who to sign in as, whether to ask. HDT's equivalent is bootstrap.json, written into the image by Update-HDTBootImage - so unlike MDT's it is GENERATED, and a tab that edited it as text would be editing something the next build overwrites. SO THIS TAB IS THE FACTS IT IS BUILT FROM. Same information, same place on the window, edited through the commands that own those keys - the only shape that survives a rebuild. --> <TabItem x:Name="HDTBootImageBootstrap" > <!-- THE BOOTSTRAP TAB IS THE RULES FILE, AND NOTHING ELSE. It carried two blocks: five fields for what the image is built with - share name, deploy root, sign-in account, log level, workspace id - and, under a rule, the rules that override the share for a machine that should not use it. Two blocks answering the same question is what an administrator reads as a window that has not decided, and it was reported as exactly that. MDT DOES NOT SPLIT THEM EITHER. Bootstrap.ini is rules all the way down: [Default] is a section like any other, and DeployRoot, UserID, UserDomain and UserPassword sit in it. So the account moved into the rules file with the share - bootstrap-rules.yaml may now set HDTUserId, HDTUserDomain and HDTUserPassword - and a machine no rule matches still falls back to what the image was built with. THE PASSWORD IS CLEAR TEXT IN THAT FILE, as it was in Bootstrap.ini. The file is copied INTO the boot image, so anybody holding the image already holds the credential baked into it. WHAT WAS NEVER A BOOTSTRAP SETTING WENT WHERE IT BELONGS. The share's name and log level are the SHARE's, not the boot image's; they are on the share's own row in the console tree, with the workspace id, which is fixed at creation. --> <Grid Margin="18,16,18,16"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock x:Name="HDTBootstrapRulesLabel" Grid.Row="0" FontSize="14" Margin="0,0,0,4" /> <!-- The same panel, because bootstrap-rules.yaml is written in the same vocabulary as rules.yaml. --> <Border x:Name="HDTBootstrapHelpButton" Grid.Row="0" Style="{StaticResource HDTHelpDot}" VerticalAlignment="Top"> <TextBlock Style="{StaticResource HDTHelpGlyph}" /> </Border> <TextBlock x:Name="HDTBootstrapRulesSummaryText" Grid.Row="1" Style="{StaticResource HDTHintText}" Margin="0,0,0,6" /> <Border Grid.Row="2" Background="{DynamicResource HDTPanelBrush}" BorderBrush="{DynamicResource HDTBorderBrush}" BorderThickness="1" MinHeight="90"> <TextBox x:Name="HDTBootstrapRulesBox" BorderThickness="0" Background="{DynamicResource HDTPanelBrush}" FontFamily="Consolas" FontSize="13" AcceptsReturn="True" AcceptsTab="True" TextWrapping="NoWrap" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" VerticalContentAlignment="Top" /> </Border> <TextBlock x:Name="HDTBootstrapRulesProblemText" Grid.Row="3" Margin="0,8,0,0" Foreground="{DynamicResource HDTCommandTextBrush}" TextWrapping="Wrap" Visibility="Collapsed" /> <StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0"> <TextBlock x:Name="HDTBootstrapRulesHint" Style="{StaticResource HDTHintText}" MaxWidth="470" VerticalAlignment="Center" Margin="0,0,12,0" /> <Button x:Name="HDTBootstrapRulesReloadButton" MinWidth="90" Margin="0,0,10,0" /> <Button x:Name="HDTBootstrapRulesSaveButton" MinWidth="90" Margin="0" /> </StackPanel> </Grid> </TabItem> </TabControl> <!-- the cmdlet the last action ran, and the ways out --> <!-- THE VOCABULARY PANEL, ONE OF THEM, FOR BOTH TABS. Rules and Bootstrap are the same language - bootstrap-rules.yaml sets HDTUserId the way rules.yaml sets HDTComputerName - so two panels would be two things to keep in step and one of them would drift. A POPUP, NOT A WINDOW. Somebody presses ? in the middle of editing a rule; a second window steals focus and covers the box they are typing in, and on this window that box is the whole tab. EVERY ROW COMES FROM Get-HDTConsoleRuleHelp, which derives it from Get-HDTVariableMap. Nothing here is a list of variable names: a list in markup is one that goes stale the first time a variable is added, silently, which is the whole failure mode of documentation kept inside a product. --> <Popup x:Name="HDTRuleHelpPopup" Grid.Row="1" Placement="Center" StaysOpen="False" AllowsTransparency="True" MaxWidth="900" MaxHeight="560"> <Border Background="{DynamicResource HDTPanelBrush}" BorderBrush="{DynamicResource HDTBorderBrush}" BorderThickness="1" Padding="18,14,18,14"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock x:Name="HDTRuleHelpTitleText" Grid.Row="0" FontSize="16" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="{DynamicResource HDTPanelTextBrush}" /> <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" MaxHeight="440"> <!-- A ListBox, not an ItemsControl: a reference list you cannot take anything from is half a feature. Double- clicking a row puts its Insert text into the editor. --> <ListBox x:Name="HDTRuleHelpList" BorderThickness="0" Background="Transparent" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled"> <ListBox.ItemTemplate> <DataTemplate> <Grid Margin="0,0,0,3"> <Grid.ColumnDefinitions> <ColumnDefinition Width="260" /> <ColumnDefinition Width="170" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <!-- A heading is the same three columns in bold, so nothing has to switch template on a row's type. --> <TextBlock Grid.Column="0" Text="{Binding Left}" TextWrapping="Wrap" Margin="0,0,12,0" FontFamily="Consolas, Courier New" FontSize="12" Foreground="{DynamicResource HDTPanelTextBrush}" /> <TextBlock Grid.Column="1" Text="{Binding Middle}" TextWrapping="Wrap" Margin="0,0,12,0" FontFamily="Consolas, Courier New" FontSize="12" Foreground="{DynamicResource HDTLabelBrush}" /> <TextBlock Grid.Column="2" Text="{Binding Right}" TextWrapping="Wrap" FontSize="12" Foreground="{DynamicResource HDTLabelBrush}" /> </Grid> <DataTemplate.Triggers> <DataTrigger Binding="{Binding IsHeader}" Value="True"> <Setter Property="TextBlock.FontWeight" Value="Bold" /> <Setter Property="TextBlock.FontSize" Value="13" /> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </ScrollViewer> <Button x:Name="HDTRuleHelpCloseButton" Grid.Row="2" MinWidth="90" HorizontalAlignment="Right" Margin="0,12,0,0" /> </Grid> </Border> </Popup> <Grid Grid.Row="2" Margin="24,16,24,20"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Border Grid.Row="0" Background="{DynamicResource HDTCommandBrush}" BorderBrush="{DynamicResource HDTBorderBrush}" BorderThickness="1" Padding="10,7,10,7"> <!-- AcceptsReturn, ON A BOX NOBODY TYPES INTO. One press of Save is four invocations - the three that decide what is written and the one that writes it - and a TextBox without this shows the first line of them and silently eats the rest. MaxHeight keeps four lines from pushing the buttons off the bottom of a short window. --> <TextBox x:Name="HDTBootImageCommandText" Text="" IsReadOnly="True" BorderThickness="0" AcceptsReturn="True" MaxHeight="76" VerticalScrollBarVisibility="Auto" Background="Transparent" Foreground="{DynamicResource HDTCommandTextBrush}" FontFamily="Consolas, Courier New" FontSize="12" TextWrapping="Wrap" /> </Border> <Grid Grid.Row="1" Margin="0,14,0,0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <!-- The long one, on its own, away from the ones that answer instantly. It mounts, injects and rebuilds - minutes, not milliseconds - and a mis-click on it beside Close is a coffee break nobody asked for. --> <Button Grid.Column="0" x:Name="HDTBootImageUpdateButton" MinWidth="150" /> <StackPanel Grid.Column="2" Orientation="Horizontal"> <Button x:Name="HDTBootImageSaveButton" MinWidth="96" /> <Button x:Name="HDTBootImageCloseButton" MinWidth="96" Margin="0" /> </StackPanel> </Grid> </Grid> </Grid> </Window> |