1019 lines
46 KiB
PowerShell
1019 lines
46 KiB
PowerShell
Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase, System.Xaml
|
|
Add-Type -AssemblyName System.Windows.Forms
|
|
|
|
$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$defaultSettingsPath = Join-Path $scriptRoot "certy.settings.json"
|
|
$defaultLogPath = Join-Path $scriptRoot "certy.log"
|
|
|
|
$xaml = @'
|
|
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
Title="Certy - Certificate Helper" Height="820" Width="1040"
|
|
WindowStartupLocation="CenterScreen" UseLayoutRounding="True">
|
|
<Window.Resources>
|
|
<SolidColorBrush x:Key="PrimaryBrush" Color="#1E4D8C"/>
|
|
<SolidColorBrush x:Key="AccentBrush" Color="#3CB371"/>
|
|
<SolidColorBrush x:Key="WarmBrush" Color="#F4A261"/>
|
|
<SolidColorBrush x:Key="SurfaceBrush" Color="#F6F8FB"/>
|
|
<SolidColorBrush x:Key="BorderBrush" Color="#D6DEE8"/>
|
|
<Style TargetType="GroupBox">
|
|
<Setter Property="Margin" Value="0,0,0,14"/>
|
|
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
|
<Setter Property="Background" Value="White"/>
|
|
<Setter Property="Padding" Value="6"/>
|
|
</Style>
|
|
<Style TargetType="Button">
|
|
<Setter Property="Padding" Value="10,6"/>
|
|
<Setter Property="Margin" Value="0,4,0,4"/>
|
|
</Style>
|
|
<Style TargetType="TextBox">
|
|
<Setter Property="Margin" Value="0,4,8,4"/>
|
|
</Style>
|
|
</Window.Resources>
|
|
<Grid Background="{StaticResource SurfaceBrush}" Margin="10">
|
|
<Grid.RowDefinitions>
|
|
<RowDefinition Height="Auto" />
|
|
<RowDefinition Height="*" />
|
|
</Grid.RowDefinitions>
|
|
<Border Grid.Row="0" CornerRadius="10" Background="{StaticResource PrimaryBrush}" Padding="14" Margin="0,0,0,12">
|
|
<DockPanel LastChildFill="True">
|
|
<Button Name="btnOpenSettings" Content="Settings" DockPanel.Dock="Right" Background="{StaticResource WarmBrush}" Foreground="White" Padding="12,6" Margin="8,0,0,0"/>
|
|
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left" Margin="0,0,12,0">
|
|
<Border Width="44" Height="44" Background="{StaticResource AccentBrush}" CornerRadius="8">
|
|
<Viewbox Stretch="Uniform">
|
|
<Canvas Width="32" Height="32">
|
|
<Ellipse Width="28" Height="28" Canvas.Left="2" Canvas.Top="2" Fill="White"/>
|
|
<Rectangle Width="12" Height="12" Canvas.Left="10" Canvas.Top="10" Fill="{StaticResource PrimaryBrush}"/>
|
|
</Canvas>
|
|
</Viewbox>
|
|
</Border>
|
|
</StackPanel>
|
|
<StackPanel>
|
|
<TextBlock Text="Certy - Certificate Helper" FontSize="22" FontWeight="Bold" Foreground="White"/>
|
|
<TextBlock Text="Step-by-step tools for requests, DNS, and renewals." Foreground="#E6EEF8" Margin="0,2,0,0"/>
|
|
</StackPanel>
|
|
</DockPanel>
|
|
</Border>
|
|
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
|
<StackPanel>
|
|
<StackPanel Visibility="Collapsed">
|
|
<TextBox Name="txtSettingsPath"/>
|
|
<TextBox Name="txtLogPath"/>
|
|
</StackPanel>
|
|
|
|
<GroupBox Header="Step 1: What do you need to do?">
|
|
<StackPanel Margin="6">
|
|
<TextBlock Text="Pick one path below. The screen will guide you step-by-step." Foreground="#5B6B7C" Margin="2,0,0,8"/>
|
|
<StackPanel Orientation="Horizontal">
|
|
<RadioButton Name="rbFlowCsr" Content="Create CSR placeholders + DNS + WACS" Margin="0,0,20,0" IsChecked="True"/>
|
|
<RadioButton Name="rbFlowInf" Content="Generate INF files from a list"/>
|
|
</StackPanel>
|
|
</StackPanel>
|
|
</GroupBox>
|
|
|
|
<StackPanel Name="pnlCsrFlow">
|
|
<GroupBox Header="Step 2: Build a CSR List">
|
|
<StackPanel Margin="4">
|
|
<TextBlock Text="Load a .txt list and choose how names should look before you start." Foreground="#5B6B7C" Margin="2,0,0,6"/>
|
|
<Grid Margin="4">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="160"/>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="110"/>
|
|
</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"/>
|
|
</Grid.RowDefinitions>
|
|
|
|
<TextBlock Text="List of names (.txt)" Grid.Row="0" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtDnsListInputPath" Grid.Row="0" Grid.Column="1"/>
|
|
<Button Name="btnBrowseDnsListInput" Grid.Row="0" Grid.Column="2" Content="Browse"/>
|
|
|
|
<TextBlock Text="If missing a domain, add" Grid.Row="1" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<ComboBox Name="cmbCsrDomainSuffix" Grid.Row="1" Grid.Column="1" Margin="0,4,8,4"/>
|
|
|
|
<TextBlock Text="CSR file name style" Grid.Row="2" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
|
<RadioButton Name="rbCsrFileFqdn" Content="Use full FQDN" Margin="0,0,12,0" IsChecked="True"/>
|
|
<RadioButton Name="rbCsrFileHost" Content="Use device name only"/>
|
|
</StackPanel>
|
|
|
|
<TextBlock Text="Save blank CSR files to" Grid.Row="3" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtDnsPlaceholderOutput" Grid.Row="3" Grid.Column="1"/>
|
|
<Button Name="btnBrowseDnsPlaceholderOutput" Grid.Row="3" Grid.Column="2" Content="Browse"/>
|
|
|
|
<CheckBox Name="chkDnsCreatePlaceholders" Grid.Row="4" Grid.Column="1" Content="Create blank CSR files" Margin="0,6,0,0"/>
|
|
|
|
<Button Name="btnPreviewCsr" Grid.Row="5" Grid.Column="2" Content="Preview" Width="110" HorizontalAlignment="Right" Margin="0,8,0,0"/>
|
|
<TextBox Name="txtCsrPreview" Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="3" Height="90"
|
|
Margin="0,6,0,0" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" IsReadOnly="True"/>
|
|
</Grid>
|
|
</StackPanel>
|
|
</GroupBox>
|
|
|
|
<GroupBox Header="Step 3: DNS Update (optional)">
|
|
<StackPanel Margin="4">
|
|
<TextBlock Text="Update DNS for each FQDN if needed." Foreground="#5B6B7C" Margin="2,0,0,6"/>
|
|
<Grid Margin="4">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="160"/>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="110"/>
|
|
</Grid.ColumnDefinitions>
|
|
<Grid.RowDefinitions>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
</Grid.RowDefinitions>
|
|
|
|
<TextBlock Text="DNS zone (domain)" Grid.Row="0" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtDnsZone" Grid.Row="0" Grid.Column="1"/>
|
|
|
|
<TextBlock Text="Target IP address" Grid.Row="1" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtDnsTargetIp" Grid.Row="1" Grid.Column="1"/>
|
|
|
|
<TextBlock Text="DNS server name" Grid.Row="2" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtDnsServer" Grid.Row="2" Grid.Column="1"/>
|
|
|
|
<CheckBox Name="chkDnsUpdate" Grid.Row="3" Grid.Column="1" Content="Update DNS A records" Margin="0,6,0,0" IsChecked="True"/>
|
|
</Grid>
|
|
</StackPanel>
|
|
</GroupBox>
|
|
|
|
<GroupBox Header="Step 4: Request Certificates (WACS)">
|
|
<StackPanel Margin="4">
|
|
<TextBlock Text="This runs WACS for each name and saves output files." Foreground="#5B6B7C" Margin="2,0,0,6"/>
|
|
<Grid Margin="4">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="160"/>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="110"/>
|
|
</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 Text="WACS app location" Grid.Row="0" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtWacsPath" Grid.Row="0" Grid.Column="1"/>
|
|
<Button Name="btnBrowseWacsPath" Grid.Row="0" Grid.Column="2" Content="Browse"/>
|
|
|
|
<TextBlock Text="Output folder" Grid.Row="1" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtWacsPfxFolder" Grid.Row="1" Grid.Column="1"/>
|
|
<Button Name="btnBrowseWacsPfxFolder" Grid.Row="1" Grid.Column="2" Content="Browse"/>
|
|
|
|
<TextBlock Text="Output format" Grid.Row="2" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtWacsStore" Grid.Row="2" Grid.Column="1"/>
|
|
|
|
<TextBlock Text="ACME server URL" Grid.Row="3" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtWacsBaseUri" Grid.Row="3" Grid.Column="1"/>
|
|
|
|
<TextBlock Text="Validation method" Grid.Row="4" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtWacsValidation" Grid.Row="4" Grid.Column="1"/>
|
|
|
|
<TextBlock Text="Validation port" Grid.Row="5" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtWacsValidationPort" Grid.Row="5" Grid.Column="1"/>
|
|
|
|
<TextBlock Text="Request type" Grid.Row="6" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtWacsTarget" Grid.Row="6" Grid.Column="1"/>
|
|
|
|
<CheckBox Name="chkWacsAddSans" Grid.Row="7" Grid.Column="1" Content="Add extra SANs (optional)" Margin="0,6,0,0"/>
|
|
<TextBox Name="txtWacsSans" Grid.Row="8" Grid.Column="0" Grid.ColumnSpan="3" Height="70"
|
|
Margin="0,6,0,0" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"/>
|
|
|
|
<StackPanel Grid.Row="9" Grid.Column="1" Orientation="Horizontal" Margin="0,4,0,4">
|
|
<CheckBox Name="chkWacsVerbose" Content="Show extra details"/>
|
|
</StackPanel>
|
|
|
|
<Button Name="btnRunWacs" Grid.Row="10" Grid.Column="2" Content="Start" Width="110" Background="{StaticResource AccentBrush}" Foreground="White" HorizontalAlignment="Right" Margin="0,8,0,0"/>
|
|
</Grid>
|
|
</StackPanel>
|
|
</GroupBox>
|
|
</StackPanel>
|
|
|
|
<StackPanel Name="pnlInfFlow">
|
|
<GroupBox Header="Step 2: Generate INF Files">
|
|
<StackPanel Margin="4">
|
|
<TextBlock Text="Use this when a request needs INF files (with SANs if needed)." Foreground="#5B6B7C" Margin="2,0,0,6"/>
|
|
<Grid Margin="4">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="160"/>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="110"/>
|
|
</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"/>
|
|
</Grid.RowDefinitions>
|
|
|
|
<TextBlock Text="List of names (.txt)" Grid.Row="0" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtInfInputPath" Grid.Row="0" Grid.Column="1"/>
|
|
<Button Name="btnBrowseInfInput" Grid.Row="0" Grid.Column="2" Content="Browse"/>
|
|
|
|
<TextBlock Text="Save INF files to" Grid.Row="1" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtInfOutputDir" Grid.Row="1" Grid.Column="1"/>
|
|
<Button Name="btnBrowseInfOutput" Grid.Row="1" Grid.Column="2" Content="Browse"/>
|
|
|
|
<TextBlock Text="Template preset" Grid.Row="2" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<ComboBox Name="cmbInfTemplatePreset" Grid.Row="2" Grid.Column="1" Margin="0,4,8,4"/>
|
|
|
|
<TextBlock Text="Primary domain suffix" Grid.Row="3" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Center"/>
|
|
<ComboBox Name="cmbInfPrimaryDomain" Grid.Row="3" Grid.Column="1" Margin="0,4,8,4"/>
|
|
|
|
<TextBlock Text="Extra SAN domains (one per line)" Grid.Row="4" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Top"/>
|
|
<TextBox Name="txtInfAdditionalDomains" Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2"
|
|
Margin="0,4,0,4" Height="70" AcceptsReturn="True"
|
|
TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"/>
|
|
|
|
<CheckBox Name="chkInfAdditionalSans" Grid.Row="5" Grid.Column="1" Content="Add SANs from the domain list" Margin="0,6,0,0"/>
|
|
|
|
<TextBlock Text="Template (advanced)" Grid.Row="6" Grid.Column="0" Margin="0,4,8,4" VerticalAlignment="Top"/>
|
|
<TextBox Name="txtInfTemplate" Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="2"
|
|
Margin="0,4,0,4" Height="170" AcceptsReturn="True"
|
|
TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"/>
|
|
|
|
<Button Name="btnRunInf" Grid.Row="7" Grid.Column="2" Content="Start" Width="110" Background="{StaticResource AccentBrush}" Foreground="White" HorizontalAlignment="Right" Margin="0,8,0,0"/>
|
|
</Grid>
|
|
</StackPanel>
|
|
</GroupBox>
|
|
</StackPanel>
|
|
|
|
<GroupBox Header="What Happened (Logs)">
|
|
<StackPanel Margin="4">
|
|
<TextBlock Text="Live notes show here and in the log file." Foreground="#5B6B7C" Margin="2,0,0,6"/>
|
|
<Grid Margin="4">
|
|
<Grid.RowDefinitions>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="*"/>
|
|
</Grid.RowDefinitions>
|
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,0,0,8">
|
|
<Button Name="btnClearLog" Content="Clear Screen Log" Width="150" Background="{StaticResource WarmBrush}" Foreground="White"/>
|
|
</StackPanel>
|
|
<TextBox Name="txtLogOutput" Grid.Row="1" Height="180" AcceptsReturn="True" TextWrapping="Wrap"
|
|
VerticalScrollBarVisibility="Auto" IsReadOnly="True"/>
|
|
</Grid>
|
|
</StackPanel>
|
|
</GroupBox>
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
</Grid>
|
|
</Window>
|
|
'@
|
|
|
|
$settingsXaml = @'
|
|
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
Title="Certy Settings" Height="380" Width="540"
|
|
WindowStartupLocation="CenterOwner" ResizeMode="NoResize" UseLayoutRounding="True">
|
|
<Grid Margin="12">
|
|
<Grid.RowDefinitions>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
</Grid.RowDefinitions>
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="150"/>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="90"/>
|
|
</Grid.ColumnDefinitions>
|
|
|
|
<TextBlock Text="Settings file" Grid.Row="0" Grid.Column="0" Margin="0,6,8,6" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtSettingsPathWin" Grid.Row="0" Grid.Column="1" Margin="0,6,8,6"/>
|
|
<Button Name="btnBrowseSettingsWin" Grid.Row="0" Grid.Column="2" Content="Browse" Margin="0,6,0,6"/>
|
|
|
|
<TextBlock Text="Activity log file" Grid.Row="1" Grid.Column="0" Margin="0,6,8,6" VerticalAlignment="Center"/>
|
|
<TextBox Name="txtLogPathWin" Grid.Row="1" Grid.Column="1" Margin="0,6,8,6"/>
|
|
<Button Name="btnBrowseLogWin" Grid.Row="1" Grid.Column="2" Content="Browse" Margin="0,6,0,6"/>
|
|
|
|
<TextBlock Text="FQDN suffix list (one per line)" Grid.Row="2" Grid.Column="0" Margin="0,6,8,6" VerticalAlignment="Top"/>
|
|
<TextBox Name="txtFqdnListWin" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Height="80" Margin="0,6,0,6"
|
|
AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"/>
|
|
|
|
<StackPanel Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3" Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,0,0">
|
|
<Button Name="btnSaveSettingsWin" Content="Save These Settings" Width="160" Margin="0,0,8,0"/>
|
|
<Button Name="btnLoadSettingsWin" Content="Load Saved Settings" Width="160"/>
|
|
</StackPanel>
|
|
|
|
<StackPanel Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,14,0,0">
|
|
<Button Name="btnCloseSettingsWin" Content="Close" Width="90"/>
|
|
</StackPanel>
|
|
</Grid>
|
|
</Window>
|
|
'@
|
|
|
|
[xml]$xamlDoc = $xaml
|
|
$reader = New-Object System.Xml.XmlNodeReader $xamlDoc
|
|
$window = [Windows.Markup.XamlReader]::Load($reader)
|
|
|
|
function Get-Control([string]$name) {
|
|
return $window.FindName($name)
|
|
}
|
|
|
|
$txtSettingsPath = Get-Control "txtSettingsPath"
|
|
$txtLogPath = Get-Control "txtLogPath"
|
|
$btnOpenSettings = Get-Control "btnOpenSettings"
|
|
$rbFlowCsr = Get-Control "rbFlowCsr"
|
|
$rbFlowInf = Get-Control "rbFlowInf"
|
|
$pnlCsrFlow = Get-Control "pnlCsrFlow"
|
|
$pnlInfFlow = Get-Control "pnlInfFlow"
|
|
|
|
$txtInfInputPath = Get-Control "txtInfInputPath"
|
|
$txtInfOutputDir = Get-Control "txtInfOutputDir"
|
|
$txtInfTemplate = Get-Control "txtInfTemplate"
|
|
$chkInfAdditionalSans = Get-Control "chkInfAdditionalSans"
|
|
$cmbInfTemplatePreset = Get-Control "cmbInfTemplatePreset"
|
|
$cmbInfPrimaryDomain = Get-Control "cmbInfPrimaryDomain"
|
|
$txtInfAdditionalDomains = Get-Control "txtInfAdditionalDomains"
|
|
$btnBrowseInfInput = Get-Control "btnBrowseInfInput"
|
|
$btnBrowseInfOutput = Get-Control "btnBrowseInfOutput"
|
|
$btnRunInf = Get-Control "btnRunInf"
|
|
|
|
$txtDnsListInputPath = Get-Control "txtDnsListInputPath"
|
|
$cmbCsrDomainSuffix = Get-Control "cmbCsrDomainSuffix"
|
|
$rbCsrFileFqdn = Get-Control "rbCsrFileFqdn"
|
|
$rbCsrFileHost = Get-Control "rbCsrFileHost"
|
|
$btnPreviewCsr = Get-Control "btnPreviewCsr"
|
|
$txtCsrPreview = Get-Control "txtCsrPreview"
|
|
$txtDnsZone = Get-Control "txtDnsZone"
|
|
$txtDnsTargetIp = Get-Control "txtDnsTargetIp"
|
|
$txtDnsServer = Get-Control "txtDnsServer"
|
|
$txtDnsPlaceholderOutput = Get-Control "txtDnsPlaceholderOutput"
|
|
$chkDnsCreatePlaceholders = Get-Control "chkDnsCreatePlaceholders"
|
|
$chkDnsUpdate = Get-Control "chkDnsUpdate"
|
|
$btnBrowseDnsListInput = Get-Control "btnBrowseDnsListInput"
|
|
$btnBrowseDnsPlaceholderOutput = Get-Control "btnBrowseDnsPlaceholderOutput"
|
|
|
|
$txtWacsPath = Get-Control "txtWacsPath"
|
|
$txtWacsPfxFolder = Get-Control "txtWacsPfxFolder"
|
|
$txtWacsBaseUri = Get-Control "txtWacsBaseUri"
|
|
$txtWacsValidation = Get-Control "txtWacsValidation"
|
|
$txtWacsValidationPort = Get-Control "txtWacsValidationPort"
|
|
$txtWacsTarget = Get-Control "txtWacsTarget"
|
|
$txtWacsStore = Get-Control "txtWacsStore"
|
|
$chkWacsAddSans = Get-Control "chkWacsAddSans"
|
|
$txtWacsSans = Get-Control "txtWacsSans"
|
|
$chkWacsVerbose = Get-Control "chkWacsVerbose"
|
|
$btnBrowseWacsPath = Get-Control "btnBrowseWacsPath"
|
|
$btnBrowseWacsPfxFolder = Get-Control "btnBrowseWacsPfxFolder"
|
|
$btnRunWacs = Get-Control "btnRunWacs"
|
|
|
|
$txtLogOutput = Get-Control "txtLogOutput"
|
|
$btnClearLog = Get-Control "btnClearLog"
|
|
|
|
$txtSettingsPath.Text = $defaultSettingsPath
|
|
$txtLogPath.Text = $defaultLogPath
|
|
$script:FqdnListText = ""
|
|
$pnlCsrFlow.Visibility = "Visible"
|
|
$pnlInfFlow.Visibility = "Collapsed"
|
|
|
|
$defaultTemplate = @'
|
|
[Version]
|
|
Signature="$Windows NT$"
|
|
[NewRequest]
|
|
Subject = "CN=$Placeholder"
|
|
KeySpec = 1
|
|
KeyLength = 2048
|
|
Exportable = TRUE
|
|
MachineKeySet = True
|
|
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
|
|
HashAlgorithm = sha256
|
|
RequestType = PKCS10
|
|
KeyUsage = 0xa0
|
|
FriendlyName = "$Placeholder"
|
|
[EnhancedKeyUsageExtension]
|
|
OID=1.3.6.1.5.5.7.3.1 ; Server Authentication
|
|
OID=1.3.6.1.5.5.7.3.2 ; Client Authentication
|
|
'@
|
|
|
|
$legacyTemplate = @'
|
|
[Version]
|
|
Signature="$Windows NT$"
|
|
[NewRequest]
|
|
Subject = "CN=$Placeholder.printer.MBIE.govt.nz;OU=ICT;O=Ministry of Business, Innovation and Employment;L=Wellington;S=Wellington;C=NZ"
|
|
X500NameFlags = 0x40000000
|
|
Exportable = TRUE
|
|
KeyLength = 2048
|
|
KeySpec = 1
|
|
KeyUsage = 0xA0
|
|
MachineKeySet = True
|
|
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
|
|
HashAlgorithm = sha256
|
|
RequestType = PKCS10
|
|
FriendlyName = "$Placeholder.printer.MBIE.govt.nz - 2026"
|
|
[EnhancedKeyUsageExtension]
|
|
OID=1.3.6.1.5.5.7.3.1 ; Server Authentication
|
|
OID=1.3.6.1.5.5.7.3.2 ; Client Authentication
|
|
'@
|
|
|
|
$txtInfTemplate.Text = $defaultTemplate
|
|
$chkInfAdditionalSans.IsChecked = $false
|
|
|
|
$txtDnsListInputPath.Text = ''
|
|
$txtDnsZone.Text = 'record.domain.govt.nz'
|
|
$txtDnsTargetIp.Text = 'managementboxIP'
|
|
$txtDnsServer.Text = 'DC01.example.local'
|
|
$txtDnsPlaceholderOutput.Text = 'C:\Temp\CSRs'
|
|
$chkDnsCreatePlaceholders.IsChecked = $false
|
|
$chkDnsUpdate.IsChecked = $true
|
|
|
|
$txtWacsPath.Text = 'C:\ProgramData\Wacs\wacs.exe'
|
|
$txtWacsPfxFolder.Text = 'C:\ProgramData\Wacs\output'
|
|
$txtWacsBaseUri.Text = 'https://acmeprod.domain.govt.nz:9999/acme/rsa/'
|
|
$txtWacsValidation.Text = 'selfhosting'
|
|
$txtWacsValidationPort.Text = '9998'
|
|
$txtWacsTarget.Text = 'manual'
|
|
$txtWacsStore.Text = 'pfxfile'
|
|
$chkWacsAddSans.IsChecked = $false
|
|
$txtWacsSans.Text = ""
|
|
$chkWacsVerbose.IsChecked = $true
|
|
|
|
function Write-Log([string]$message, [string]$level = "INFO") {
|
|
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
$line = "{0} [{1}] {2}" -f $timestamp, $level, $message
|
|
$txtLogOutput.AppendText($line + [Environment]::NewLine)
|
|
$txtLogOutput.ScrollToEnd()
|
|
|
|
$logPath = $txtLogPath.Text.Trim()
|
|
if ([string]::IsNullOrWhiteSpace($logPath)) { return }
|
|
|
|
try {
|
|
$logDir = Split-Path -Parent $logPath
|
|
if ($logDir -and -not (Test-Path $logDir)) {
|
|
New-Item -Path $logDir -ItemType Directory -Force | Out-Null
|
|
}
|
|
Add-Content -Path $logPath -Value $line -Encoding ascii
|
|
} catch {
|
|
$txtLogOutput.AppendText("Failed to write log file: $($_.Exception.Message)" + [Environment]::NewLine)
|
|
}
|
|
}
|
|
|
|
function Save-Settings {
|
|
$settingsPath = $txtSettingsPath.Text.Trim()
|
|
if ([string]::IsNullOrWhiteSpace($settingsPath)) {
|
|
Write-Log "Settings path is empty." "ERROR"
|
|
return
|
|
}
|
|
|
|
$settings = @{
|
|
SettingsPath = $settingsPath
|
|
LogPath = $txtLogPath.Text.Trim()
|
|
FqdnList = $(if ($script:FqdnListText) { $script:FqdnListText } else { "" })
|
|
Inf = @{
|
|
InputPath = $txtInfInputPath.Text.Trim()
|
|
OutputDir = $txtInfOutputDir.Text.Trim()
|
|
Template = $txtInfTemplate.Text
|
|
IncludeAdditionalSans = [bool]$chkInfAdditionalSans.IsChecked
|
|
PrimaryDomain = $cmbInfPrimaryDomain.Text.Trim()
|
|
AdditionalDomains = $txtInfAdditionalDomains.Text
|
|
TemplatePreset = $cmbInfTemplatePreset.Text
|
|
}
|
|
Dns = @{
|
|
ListInputPath = $txtDnsListInputPath.Text.Trim()
|
|
Zone = $txtDnsZone.Text.Trim()
|
|
TargetIp = $txtDnsTargetIp.Text.Trim()
|
|
DnsServer = $txtDnsServer.Text.Trim()
|
|
PlaceholderOutput = $txtDnsPlaceholderOutput.Text.Trim()
|
|
CreatePlaceholders = [bool]$chkDnsCreatePlaceholders.IsChecked
|
|
UpdateDns = [bool]$chkDnsUpdate.IsChecked
|
|
}
|
|
Wacs = @{
|
|
WacsPath = $txtWacsPath.Text.Trim()
|
|
PfxFolder = $txtWacsPfxFolder.Text.Trim()
|
|
BaseUri = $txtWacsBaseUri.Text.Trim()
|
|
Validation = $txtWacsValidation.Text.Trim()
|
|
ValidationPort = $txtWacsValidationPort.Text.Trim()
|
|
Target = $txtWacsTarget.Text.Trim()
|
|
Store = $txtWacsStore.Text.Trim()
|
|
AddSans = [bool]$chkWacsAddSans.IsChecked
|
|
Sans = $txtWacsSans.Text
|
|
Verbose = [bool]$chkWacsVerbose.IsChecked
|
|
}
|
|
SavedAt = (Get-Date).ToString("s")
|
|
}
|
|
|
|
try {
|
|
$settingsDir = Split-Path -Parent $settingsPath
|
|
if ($settingsDir -and -not (Test-Path $settingsDir)) {
|
|
New-Item -Path $settingsDir -ItemType Directory -Force | Out-Null
|
|
}
|
|
$json = $settings | ConvertTo-Json -Depth 6
|
|
Set-Content -Path $settingsPath -Value $json -Encoding ascii
|
|
Write-Log "Settings saved to $settingsPath"
|
|
} catch {
|
|
Write-Log "Failed to save settings: $($_.Exception.Message)" "ERROR"
|
|
}
|
|
}
|
|
|
|
function Load-Settings {
|
|
$settingsPath = $txtSettingsPath.Text.Trim()
|
|
if (-not (Test-Path $settingsPath -PathType Leaf)) {
|
|
Write-Log "Settings file not found: $settingsPath" "ERROR"
|
|
return
|
|
}
|
|
|
|
try {
|
|
$raw = Get-Content -Path $settingsPath -Raw
|
|
$settings = $raw | ConvertFrom-Json
|
|
|
|
if ($settings.LogPath) { $txtLogPath.Text = $settings.LogPath }
|
|
|
|
if ($settings.FqdnList) { $script:FqdnListText = $settings.FqdnList }
|
|
|
|
if ($settings.Inf) {
|
|
$txtInfInputPath.Text = $settings.Inf.InputPath
|
|
$txtInfOutputDir.Text = $settings.Inf.OutputDir
|
|
if ($settings.Inf.Template) { $txtInfTemplate.Text = $settings.Inf.Template }
|
|
if ($settings.Inf.TemplatePreset) { $cmbInfTemplatePreset.Text = $settings.Inf.TemplatePreset }
|
|
if ($settings.Inf.PrimaryDomain) { $cmbInfPrimaryDomain.Text = $settings.Inf.PrimaryDomain }
|
|
if ($settings.Inf.AdditionalDomains) { $txtInfAdditionalDomains.Text = $settings.Inf.AdditionalDomains }
|
|
if ($settings.Inf.PSObject.Properties.Name -contains "IncludeAdditionalSans") {
|
|
$chkInfAdditionalSans.IsChecked = [bool]$settings.Inf.IncludeAdditionalSans
|
|
} elseif ($settings.Inf.PSObject.Properties.Name -contains "UseSimpleTemplate") {
|
|
$chkInfAdditionalSans.IsChecked = -not [bool]$settings.Inf.UseSimpleTemplate
|
|
}
|
|
}
|
|
|
|
if ($settings.Dns) {
|
|
if ($settings.Dns.ListInputPath) { $txtDnsListInputPath.Text = $settings.Dns.ListInputPath }
|
|
if ($settings.Dns.Zone) { $txtDnsZone.Text = $settings.Dns.Zone }
|
|
if ($settings.Dns.TargetIp) { $txtDnsTargetIp.Text = $settings.Dns.TargetIp }
|
|
if ($settings.Dns.DnsServer) { $txtDnsServer.Text = $settings.Dns.DnsServer }
|
|
if ($settings.Dns.PlaceholderOutput) { $txtDnsPlaceholderOutput.Text = $settings.Dns.PlaceholderOutput }
|
|
if ($settings.Dns.PSObject.Properties.Name -contains "CreatePlaceholders") { $chkDnsCreatePlaceholders.IsChecked = [bool]$settings.Dns.CreatePlaceholders }
|
|
if ($settings.Dns.PSObject.Properties.Name -contains "UpdateDns") { $chkDnsUpdate.IsChecked = [bool]$settings.Dns.UpdateDns }
|
|
}
|
|
|
|
if ($settings.Wacs) {
|
|
if ($settings.Wacs.WacsPath) { $txtWacsPath.Text = $settings.Wacs.WacsPath }
|
|
if ($settings.Wacs.PfxFolder) { $txtWacsPfxFolder.Text = $settings.Wacs.PfxFolder }
|
|
if ($settings.Wacs.BaseUri) { $txtWacsBaseUri.Text = $settings.Wacs.BaseUri }
|
|
if ($settings.Wacs.Validation) { $txtWacsValidation.Text = $settings.Wacs.Validation }
|
|
if ($settings.Wacs.ValidationPort) { $txtWacsValidationPort.Text = $settings.Wacs.ValidationPort }
|
|
if ($settings.Wacs.Target) { $txtWacsTarget.Text = $settings.Wacs.Target }
|
|
if ($settings.Wacs.Store) { $txtWacsStore.Text = $settings.Wacs.Store }
|
|
if ($settings.Wacs.PSObject.Properties.Name -contains "AddSans") { $chkWacsAddSans.IsChecked = [bool]$settings.Wacs.AddSans }
|
|
if ($settings.Wacs.PSObject.Properties.Name -contains "Sans") { $txtWacsSans.Text = $settings.Wacs.Sans }
|
|
$chkWacsVerbose.IsChecked = [bool]$settings.Wacs.Verbose
|
|
}
|
|
|
|
Refresh-FqdnLists
|
|
|
|
Write-Log "Settings loaded from $settingsPath"
|
|
} catch {
|
|
Write-Log "Failed to load settings: $($_.Exception.Message)" "ERROR"
|
|
}
|
|
}
|
|
|
|
function Open-FileDialog([string]$filter = "All files (*.*)|*.*") {
|
|
$dialog = New-Object Microsoft.Win32.OpenFileDialog
|
|
$dialog.Filter = $filter
|
|
$dialog.Multiselect = $false
|
|
if ($dialog.ShowDialog() -eq $true) { return $dialog.FileName }
|
|
return $null
|
|
}
|
|
|
|
function Open-FolderDialog {
|
|
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
|
|
if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
|
return $dialog.SelectedPath
|
|
}
|
|
return $null
|
|
}
|
|
|
|
function Get-FqdnSuffixes {
|
|
if ([string]::IsNullOrWhiteSpace($script:FqdnListText)) { return @() }
|
|
return $script:FqdnListText -split "(\r?\n)" | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
|
}
|
|
|
|
function Refresh-FqdnLists {
|
|
$suffixes = Get-FqdnSuffixes
|
|
$cmbCsrDomainSuffix.Items.Clear()
|
|
$cmbInfPrimaryDomain.Items.Clear()
|
|
|
|
foreach ($suffix in $suffixes) {
|
|
$null = $cmbCsrDomainSuffix.Items.Add($suffix)
|
|
$null = $cmbInfPrimaryDomain.Items.Add($suffix)
|
|
}
|
|
|
|
if ($suffixes.Count -gt 0) {
|
|
if ([string]::IsNullOrWhiteSpace($cmbCsrDomainSuffix.Text)) { $cmbCsrDomainSuffix.SelectedIndex = 0 }
|
|
if ([string]::IsNullOrWhiteSpace($cmbInfPrimaryDomain.Text)) { $cmbInfPrimaryDomain.SelectedIndex = 0 }
|
|
}
|
|
}
|
|
|
|
function Set-InfTemplatePreset([string]$preset) {
|
|
switch ($preset) {
|
|
"Default (certmgr)" { $txtInfTemplate.Text = $defaultTemplate }
|
|
"Legacy (MBIE)" { $txtInfTemplate.Text = $legacyTemplate }
|
|
default { }
|
|
}
|
|
}
|
|
|
|
function Get-NameItemsFromFile([string]$path) {
|
|
if (-not (Test-Path -Path $path -PathType Leaf)) {
|
|
throw "Input file not found: $path"
|
|
}
|
|
return Get-Content -Path $path | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
|
}
|
|
|
|
function Get-HostLabel([string]$fqdnOrName) {
|
|
if ($fqdnOrName -match '\.') { return $fqdnOrName.Split('.')[0] }
|
|
return $fqdnOrName
|
|
}
|
|
|
|
function Resolve-Fqdn([string]$name, [string]$suffix) {
|
|
if ($name -match '\.') { return $name }
|
|
if ([string]::IsNullOrWhiteSpace($suffix)) {
|
|
throw "Missing domain suffix for name: $name"
|
|
}
|
|
return "$name.$suffix"
|
|
}
|
|
|
|
function Get-SafeFileName([string]$name) {
|
|
$invalidChars = [IO.Path]::GetInvalidFileNameChars()
|
|
return -join ($name.ToCharArray() | ForEach-Object {
|
|
if ($invalidChars -contains $_) { '_' } else { $_ }
|
|
})
|
|
}
|
|
|
|
function Build-CsrItems([string[]]$names, [string]$suffix, [bool]$useFqdnFileName) {
|
|
$items = @()
|
|
foreach ($name in $names) {
|
|
$fqdn = Resolve-Fqdn $name $suffix
|
|
$hostLabel = Get-HostLabel $fqdn
|
|
$fileBase = if ($useFqdnFileName) { $fqdn } else { $hostLabel }
|
|
$items += [PSCustomObject]@{
|
|
InputName = $name
|
|
HostLabel = $hostLabel
|
|
Fqdn = $fqdn
|
|
FileBase = $fileBase
|
|
}
|
|
}
|
|
return $items
|
|
}
|
|
|
|
function Get-SanValues([string]$hostLabel, [string]$suffix, [string]$rawText) {
|
|
$values = @()
|
|
if ([string]::IsNullOrWhiteSpace($rawText)) { return $values }
|
|
|
|
$rawItems = $rawText -split "(\r?\n|,)" | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
|
foreach ($item in $rawItems) {
|
|
$value = $item.Replace("{host}", $hostLabel)
|
|
if ($value -notmatch '\.' -and -not [string]::IsNullOrWhiteSpace($suffix)) {
|
|
$value = "$value.$suffix"
|
|
}
|
|
$values += $value
|
|
}
|
|
|
|
return $values | Select-Object -Unique
|
|
}
|
|
|
|
function Build-SanBlock([string[]]$fqdns) {
|
|
if (-not $fqdns -or $fqdns.Count -eq 0) { return "" }
|
|
$lines = @(
|
|
"[Extensions]",
|
|
"2.5.29.17 = ""{text}"""
|
|
)
|
|
foreach ($fqdn in $fqdns) {
|
|
$lines += "_continue_ = ""dns=$fqdn&"""
|
|
}
|
|
return ($lines -join "`r`n")
|
|
}
|
|
|
|
$cmbInfTemplatePreset.Items.Clear()
|
|
$cmbInfTemplatePreset.Items.Clear()
|
|
$null = $cmbInfTemplatePreset.Items.Add("Default (certmgr)")
|
|
$null = $cmbInfTemplatePreset.Items.Add("Legacy (MBIE)")
|
|
$null = $cmbInfTemplatePreset.Items.Add("Custom (edit below)")
|
|
if ([string]::IsNullOrWhiteSpace($cmbInfTemplatePreset.Text)) {
|
|
$cmbInfTemplatePreset.SelectedIndex = 0
|
|
Set-InfTemplatePreset "Default (certmgr)"
|
|
}
|
|
|
|
$cmbInfTemplatePreset.Add_SelectionChanged({
|
|
$selected = $cmbInfTemplatePreset.SelectedItem
|
|
if ($selected) { Set-InfTemplatePreset $selected.ToString() }
|
|
})
|
|
|
|
Refresh-FqdnLists
|
|
|
|
if (Test-Path -Path $txtSettingsPath.Text -PathType Leaf) {
|
|
Load-Settings
|
|
}
|
|
|
|
$btnOpenSettings.Add_Click({
|
|
try {
|
|
[xml]$settingsDoc = $settingsXaml
|
|
$settingsReader = New-Object System.Xml.XmlNodeReader $settingsDoc
|
|
$settingsWindow = [Windows.Markup.XamlReader]::Load($settingsReader)
|
|
|
|
function Get-SettingsControl([string]$name) {
|
|
return $settingsWindow.FindName($name)
|
|
}
|
|
|
|
$txtSettingsPathWin = Get-SettingsControl "txtSettingsPathWin"
|
|
$txtLogPathWin = Get-SettingsControl "txtLogPathWin"
|
|
$btnBrowseSettingsWin = Get-SettingsControl "btnBrowseSettingsWin"
|
|
$btnBrowseLogWin = Get-SettingsControl "btnBrowseLogWin"
|
|
$txtFqdnListWin = Get-SettingsControl "txtFqdnListWin"
|
|
$btnSaveSettingsWin = Get-SettingsControl "btnSaveSettingsWin"
|
|
$btnLoadSettingsWin = Get-SettingsControl "btnLoadSettingsWin"
|
|
$btnCloseSettingsWin = Get-SettingsControl "btnCloseSettingsWin"
|
|
|
|
$txtSettingsPathWin.Text = $txtSettingsPath.Text
|
|
$txtLogPathWin.Text = $txtLogPath.Text
|
|
$txtFqdnListWin.Text = $script:FqdnListText
|
|
|
|
$btnBrowseSettingsWin.Add_Click({
|
|
$path = Open-FileDialog "JSON (*.json)|*.json|All files (*.*)|*.*"
|
|
if ($path) { $txtSettingsPathWin.Text = $path }
|
|
})
|
|
|
|
$btnBrowseLogWin.Add_Click({
|
|
$path = Open-FileDialog "Log (*.log)|*.log|All files (*.*)|*.*"
|
|
if ($path) { $txtLogPathWin.Text = $path }
|
|
})
|
|
|
|
$btnSaveSettingsWin.Add_Click({
|
|
$txtSettingsPath.Text = $txtSettingsPathWin.Text
|
|
$txtLogPath.Text = $txtLogPathWin.Text
|
|
$script:FqdnListText = $txtFqdnListWin.Text
|
|
Refresh-FqdnLists
|
|
Save-Settings
|
|
})
|
|
|
|
$btnLoadSettingsWin.Add_Click({
|
|
$txtSettingsPath.Text = $txtSettingsPathWin.Text
|
|
Load-Settings
|
|
$txtSettingsPathWin.Text = $txtSettingsPath.Text
|
|
$txtLogPathWin.Text = $txtLogPath.Text
|
|
$txtFqdnListWin.Text = $script:FqdnListText
|
|
})
|
|
|
|
$btnCloseSettingsWin.Add_Click({ $settingsWindow.Close() })
|
|
|
|
$settingsWindow.Owner = $window
|
|
$settingsWindow.ShowDialog() | Out-Null
|
|
} catch {
|
|
Write-Log "Failed to open settings: $($_.Exception.Message)" "ERROR"
|
|
}
|
|
})
|
|
|
|
$btnBrowseInfInput.Add_Click({
|
|
$path = Open-FileDialog "Text files (*.txt)|*.txt|All files (*.*)|*.*"
|
|
if ($path) { $txtInfInputPath.Text = $path }
|
|
})
|
|
|
|
$btnBrowseInfOutput.Add_Click({
|
|
$path = Open-FolderDialog
|
|
if ($path) { $txtInfOutputDir.Text = $path }
|
|
})
|
|
|
|
$btnBrowseDnsListInput.Add_Click({
|
|
$path = Open-FileDialog "Text files (*.txt)|*.txt|All files (*.*)|*.*"
|
|
if ($path) { $txtDnsListInputPath.Text = $path }
|
|
})
|
|
|
|
$btnBrowseDnsPlaceholderOutput.Add_Click({
|
|
$path = Open-FolderDialog
|
|
if ($path) { $txtDnsPlaceholderOutput.Text = $path }
|
|
})
|
|
|
|
$btnBrowseWacsPath.Add_Click({
|
|
$path = Open-FileDialog "Executable (*.exe)|*.exe|All files (*.*)|*.*"
|
|
if ($path) { $txtWacsPath.Text = $path }
|
|
})
|
|
|
|
$btnBrowseWacsPfxFolder.Add_Click({
|
|
$path = Open-FolderDialog
|
|
if ($path) { $txtWacsPfxFolder.Text = $path }
|
|
})
|
|
|
|
$btnClearLog.Add_Click({
|
|
$txtLogOutput.Clear()
|
|
})
|
|
|
|
$rbFlowCsr.Add_Click({
|
|
$pnlCsrFlow.Visibility = "Visible"
|
|
$pnlInfFlow.Visibility = "Collapsed"
|
|
})
|
|
|
|
$rbFlowInf.Add_Click({
|
|
$pnlCsrFlow.Visibility = "Collapsed"
|
|
$pnlInfFlow.Visibility = "Visible"
|
|
})
|
|
|
|
$btnPreviewCsr.Add_Click({
|
|
try {
|
|
$listPath = $txtDnsListInputPath.Text.Trim()
|
|
$suffix = $cmbCsrDomainSuffix.Text.Trim()
|
|
$names = Get-NameItemsFromFile $listPath
|
|
$useFqdnFileName = [bool]$rbCsrFileFqdn.IsChecked
|
|
$items = Build-CsrItems $names $suffix $useFqdnFileName
|
|
|
|
$previewLines = $items | ForEach-Object {
|
|
"{0} -> {1} -> {2}.csr" -f $_.InputName, $_.Fqdn, $_.FileBase
|
|
}
|
|
$txtCsrPreview.Text = ($previewLines -join "`r`n")
|
|
} catch {
|
|
Write-Log "Preview failed: $($_.Exception.Message)" "ERROR"
|
|
}
|
|
})
|
|
|
|
$btnRunInf.Add_Click({
|
|
try {
|
|
$inputPath = $txtInfInputPath.Text.Trim()
|
|
$outputDir = $txtInfOutputDir.Text.Trim()
|
|
$template = $txtInfTemplate.Text
|
|
$placeholderToken = '$Placeholder'
|
|
$primaryDomain = $cmbInfPrimaryDomain.Text.Trim()
|
|
$additionalDomains = @()
|
|
if (-not [string]::IsNullOrWhiteSpace($txtInfAdditionalDomains.Text)) {
|
|
$additionalDomains = $txtInfAdditionalDomains.Text -split "(\r?\n|,)" | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
|
}
|
|
|
|
if (-not (Test-Path -Path $inputPath -PathType Leaf)) {
|
|
throw "Input file not found: $inputPath"
|
|
}
|
|
if (-not (Test-Path -Path $outputDir -PathType Container)) {
|
|
throw "Output directory not found: $outputDir"
|
|
}
|
|
|
|
$lines = Get-Content -Path $inputPath
|
|
$count = 0
|
|
|
|
foreach ($line in $lines) {
|
|
$name = $line.Trim()
|
|
if ([string]::IsNullOrWhiteSpace($name)) { continue }
|
|
|
|
$hostLabel = Get-HostLabel $name
|
|
$primaryFqdn = if ($name -match '\.') { $name } else { Resolve-Fqdn $name $primaryDomain }
|
|
|
|
$content = $template.Replace($placeholderToken, $hostLabel)
|
|
if ([bool]$chkInfAdditionalSans.IsChecked) {
|
|
$sanFqdns = @($primaryFqdn)
|
|
foreach ($domain in $additionalDomains) {
|
|
$sanFqdns += "$hostLabel.$domain"
|
|
}
|
|
$sanFqdns = $sanFqdns | Select-Object -Unique
|
|
if ($content -notmatch '\[Extensions\]') {
|
|
$content = $content.TrimEnd() + "`r`n`r`n" + (Build-SanBlock $sanFqdns)
|
|
}
|
|
}
|
|
|
|
$safeName = Get-SafeFileName $hostLabel
|
|
$outPath = Join-Path -Path $outputDir -ChildPath ($safeName + ".inf")
|
|
Set-Content -Path $outPath -Value $content -Encoding ascii
|
|
$count++
|
|
Write-Log "Generated: $outPath"
|
|
}
|
|
|
|
Write-Log "INF generation completed. Files created: $count"
|
|
} catch {
|
|
Write-Log "INF generation failed: $($_.Exception.Message)" "ERROR"
|
|
}
|
|
})
|
|
|
|
$btnRunWacs.Add_Click({
|
|
try {
|
|
$listPath = $txtDnsListInputPath.Text.Trim()
|
|
$suffix = $cmbCsrDomainSuffix.Text.Trim()
|
|
$wacsPath = $txtWacsPath.Text.Trim()
|
|
$pfxFolder = $txtWacsPfxFolder.Text.Trim()
|
|
$baseUri = $txtWacsBaseUri.Text.Trim()
|
|
$validation = $txtWacsValidation.Text.Trim()
|
|
$validationPort = $txtWacsValidationPort.Text.Trim()
|
|
$target = $txtWacsTarget.Text.Trim()
|
|
$store = $txtWacsStore.Text.Trim()
|
|
$createPlaceholders = [bool]$chkDnsCreatePlaceholders.IsChecked
|
|
$updateDns = [bool]$chkDnsUpdate.IsChecked
|
|
$dnsZone = $txtDnsZone.Text.Trim()
|
|
$targetIp = $txtDnsTargetIp.Text.Trim()
|
|
$dnsServer = $txtDnsServer.Text.Trim()
|
|
$placeholderOutput = $txtDnsPlaceholderOutput.Text.Trim()
|
|
$addSans = [bool]$chkWacsAddSans.IsChecked
|
|
$rawSans = $txtWacsSans.Text
|
|
$verbose = [bool]$chkWacsVerbose.IsChecked
|
|
|
|
$names = Get-NameItemsFromFile $listPath
|
|
$useFqdnFileName = [bool]$rbCsrFileFqdn.IsChecked
|
|
$items = Build-CsrItems $names $suffix $useFqdnFileName
|
|
|
|
if (-not (Test-Path -Path $wacsPath -PathType Leaf)) {
|
|
throw "WACS executable not found: $wacsPath"
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($pfxFolder)) { throw "PFX output folder is required." }
|
|
if (-not (Test-Path -Path $pfxFolder -PathType Container)) {
|
|
New-Item -Path $pfxFolder -ItemType Directory -Force | Out-Null
|
|
}
|
|
|
|
if ($createPlaceholders) {
|
|
if ([string]::IsNullOrWhiteSpace($placeholderOutput)) {
|
|
throw "Placeholder output folder is required when creating CSR placeholders."
|
|
}
|
|
if (-not (Test-Path -Path $placeholderOutput -PathType Container)) {
|
|
New-Item -Path $placeholderOutput -ItemType Directory -Force | Out-Null
|
|
}
|
|
foreach ($item in $items) {
|
|
$safeName = Get-SafeFileName $item.FileBase
|
|
$outPath = Join-Path -Path $placeholderOutput -ChildPath ($safeName + ".csr")
|
|
Set-Content -Path $outPath -Value "" -Encoding ascii
|
|
Write-Log "Created placeholder CSR: $outPath"
|
|
}
|
|
}
|
|
|
|
if ($updateDns) {
|
|
if ([string]::IsNullOrWhiteSpace($dnsZone)) { throw "DNS zone is required." }
|
|
if ([string]::IsNullOrWhiteSpace($targetIp)) { throw "Target IP is required." }
|
|
if ([string]::IsNullOrWhiteSpace($dnsServer)) { throw "DNS server is required." }
|
|
|
|
foreach ($item in $items) {
|
|
$fqdn = $item.Fqdn
|
|
if ([string]::IsNullOrWhiteSpace($fqdn)) { continue }
|
|
|
|
if ($fqdn.ToLower().EndsWith(".$($dnsZone.ToLower())")) {
|
|
$hostname = $fqdn.Substring(0, $fqdn.Length - $dnsZone.Length - 1)
|
|
} else {
|
|
$hostname = $fqdn
|
|
}
|
|
|
|
try {
|
|
Add-DnsServerResourceRecordA `
|
|
-Name $hostname `
|
|
-ZoneName $dnsZone `
|
|
-IPv4Address $targetIp `
|
|
-ComputerName $dnsServer `
|
|
-ErrorAction Stop
|
|
Write-Log "DNS record added: $hostname.$dnsZone -> $targetIp"
|
|
} catch {
|
|
Write-Log "DNS record failed for ${fqdn}: $($_.Exception.Message)" "WARN"
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($item in $items) {
|
|
$hostValue = $item.Fqdn
|
|
if ([string]::IsNullOrWhiteSpace($hostValue)) { continue }
|
|
|
|
$args = @(
|
|
"--target", $target,
|
|
"--host", $hostValue,
|
|
"--store", $store,
|
|
"--pfxfilepath", $pfxFolder,
|
|
"--baseuri", $baseUri,
|
|
"--validation", $validation,
|
|
"--validationport", $validationPort
|
|
)
|
|
|
|
if ($addSans) {
|
|
$sanValues = Get-SanValues $item.HostLabel $suffix $rawSans
|
|
foreach ($san in $sanValues) {
|
|
$args += @("--san", $san)
|
|
}
|
|
}
|
|
|
|
if ($verbose) { $args += "--verbose" }
|
|
|
|
Write-Log "Running WACS for $hostValue"
|
|
Write-Log "& $wacsPath $($args -join ' ')"
|
|
|
|
try {
|
|
& $wacsPath @args
|
|
Write-Log "Completed: $hostValue"
|
|
} catch {
|
|
Write-Log "WACS failed for ${hostValue}: $($_.Exception.Message)" "WARN"
|
|
}
|
|
}
|
|
|
|
Write-Log "WACS processing completed."
|
|
} catch {
|
|
Write-Log "WACS processing failed: $($_.Exception.Message)" "ERROR"
|
|
}
|
|
})
|
|
|
|
$window.ShowDialog() | Out-Null
|