Improve Certy UI and defaults

This commit is contained in:
2026-01-29 09:41:40 +13:00
parent b384c73cac
commit 44a9ce622a

173
certy.ps1
View File

@@ -69,6 +69,33 @@ function Get-FilePreview {
} }
} }
function Get-DefaultsPath {
$dir = Join-Path $env:ProgramData "Certy"
return Join-Path $dir "defaults.json"
}
function Load-Defaults {
$path = Get-DefaultsPath
if (-not (Test-Path -Path $path -PathType Leaf)) { return $null }
try {
$raw = Get-Content -Path $path -Raw -ErrorAction Stop
return $raw | ConvertFrom-Json
} catch {
return $null
}
}
function Save-Defaults {
param([pscustomobject]$Defaults)
$path = Get-DefaultsPath
$dir = Split-Path -Path $path -Parent
if (-not (Test-Path -Path $dir -PathType Container)) {
New-Item -Path $dir -ItemType Directory -Force | Out-Null
}
$json = $Defaults | ConvertTo-Json -Depth 6
Set-Content -Path $path -Value $json -Encoding ascii
}
function Resolve-HostEntry { function Resolve-HostEntry {
param( param(
[string]$Name, [string]$Name,
@@ -182,7 +209,7 @@ function Invoke-Replication {
function Invoke-Wacs { function Invoke-Wacs {
param( param(
[string]$WacsPath, [string]$WacsPath,
[string]$HostFqdn, [string[]]$HostFqdns,
[string]$OutputType, [string]$OutputType,
[string]$OutputPath, [string]$OutputPath,
[string]$PfxPassword, [string]$PfxPassword,
@@ -193,7 +220,12 @@ function Invoke-Wacs {
[scriptblock]$Log [scriptblock]$Log
) )
$args = @("--target", "manual", "--host", $HostFqdn) $args = @("--target", "manual")
foreach ($host in $HostFqdns) {
if (-not [string]::IsNullOrWhiteSpace($host)) {
$args += @("--host", $host)
}
}
if ($OutputType -eq "PEM") { if ($OutputType -eq "PEM") {
$args += @("--store", "pemfiles", "--pemfilespath", $OutputPath) $args += @("--store", "pemfiles", "--pemfilespath", $OutputPath)
@@ -255,8 +287,9 @@ $rowHeight = 24
$gap = 8 $gap = 8
$leftMargin = 20 $leftMargin = 20
$rightMargin = 20 $rightMargin = 20
$buttonWidth = 80 $buttonWidth = 110
$buttonGap = 8 $buttonGap = 10
$actionButtonWidth = 130
$navTitle = New-Object System.Windows.Forms.Label $navTitle = New-Object System.Windows.Forms.Label
$navTitle.Text = "CERTY" $navTitle.Text = "CERTY"
@@ -400,38 +433,38 @@ $hostsBox = Add-TextBox $xInput $y $inputWidth 100 $true
$y += 100 + $gap $y += 100 + $gap
Add-Label "Hostnames file (optional)" $xLabel $y $labelWidth $rowHeight Add-Label "Hostnames file (optional)" $xLabel $y $labelWidth $rowHeight
$fileBox = Add-TextBox $xInput $y ($inputWidth - 90) $rowHeight $false $fileBox = Add-TextBox $xInput $y ($inputWidth - ($buttonWidth + $buttonGap)) $rowHeight $false
$browseBtn = New-Object System.Windows.Forms.Button $browseBtn = New-Object System.Windows.Forms.Button
$browseBtn.Text = "Browse" $browseBtn.Text = "Browse"
$browseBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1)) $browseBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
$browseBtn.Size = [System.Drawing.Size]::new(80, 26) $browseBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($browseBtn) $panel.Controls.Add($browseBtn)
Style-ButtonSecondary $browseBtn Style-ButtonSecondary $browseBtn
$y += $rowHeight + $gap $y += $rowHeight + $gap
Add-Label "File preview (first 200 lines)" $xLabel $y $labelWidth $rowHeight Add-Label "File preview (first 200 lines)" $xLabel $y $labelWidth $rowHeight
$filePreviewBox = Add-TextBox $xInput $y ($inputWidth - 90) 80 $true $filePreviewBox = Add-TextBox $xInput $y ($inputWidth - ($buttonWidth + $buttonGap)) 80 $true
$filePreviewBox.ReadOnly = $true $filePreviewBox.ReadOnly = $true
$filePreviewBtn = New-Object System.Windows.Forms.Button $filePreviewBtn = New-Object System.Windows.Forms.Button
$filePreviewBtn.Text = "Preview" $filePreviewBtn.Text = "Preview"
$filePreviewBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1)) $filePreviewBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
$filePreviewBtn.Size = [System.Drawing.Size]::new(80, 26) $filePreviewBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($filePreviewBtn) $panel.Controls.Add($filePreviewBtn)
Style-ButtonSecondary $filePreviewBtn Style-ButtonSecondary $filePreviewBtn
$y += 82 + $gap $y += 82 + $gap
$csrLabel = Add-Label "CSR folder (optional)" $xLabel $y $labelWidth $rowHeight $csrLabel = Add-Label "CSR folder (optional)" $xLabel $y $labelWidth $rowHeight
$csrFolderBox = Add-TextBox $xInput $y ($inputWidth - 180) $rowHeight $false $csrFolderBox = Add-TextBox $xInput $y ($inputWidth - ((2 * $buttonWidth) + $buttonGap)) $rowHeight $false
$csrBrowseBtn = New-Object System.Windows.Forms.Button $csrBrowseBtn = New-Object System.Windows.Forms.Button
$csrBrowseBtn.Text = "Browse" $csrBrowseBtn.Text = "Browse"
$csrBrowseBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - (2 * $buttonWidth + $buttonGap)), ($y - 1)) $csrBrowseBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - (2 * $buttonWidth + $buttonGap)), ($y - 1))
$csrBrowseBtn.Size = [System.Drawing.Size]::new(80, 26) $csrBrowseBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($csrBrowseBtn) $panel.Controls.Add($csrBrowseBtn)
Style-ButtonSecondary $csrBrowseBtn Style-ButtonSecondary $csrBrowseBtn
$csrImportBtn = New-Object System.Windows.Forms.Button $csrImportBtn = New-Object System.Windows.Forms.Button
$csrImportBtn.Text = "Import CSR" $csrImportBtn.Text = "Import CSR"
$csrImportBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1)) $csrImportBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
$csrImportBtn.Size = [System.Drawing.Size]::new(80, 26) $csrImportBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($csrImportBtn) $panel.Controls.Add($csrImportBtn)
Style-ButtonSecondary $csrImportBtn Style-ButtonSecondary $csrImportBtn
$y += $rowHeight + $gap $y += $rowHeight + $gap
@@ -446,12 +479,12 @@ $zoneBox.Text = "record.domain.govt.nz"
$y += $rowHeight + $gap $y += $rowHeight + $gap
Add-Label "Target IPv4 for A records" $xLabel $y $labelWidth $rowHeight Add-Label "Target IPv4 for A records" $xLabel $y $labelWidth $rowHeight
$ipBox = Add-TextBox $xInput $y ($inputWidth - 90) $rowHeight $false $ipBox = Add-TextBox $xInput $y ($inputWidth - ($buttonWidth + $buttonGap)) $rowHeight $false
$ipBox.Text = Get-LocalIpv4 $ipBox.Text = Get-LocalIpv4
$ipRefreshBtn = New-Object System.Windows.Forms.Button $ipRefreshBtn = New-Object System.Windows.Forms.Button
$ipRefreshBtn.Text = "Use Local" $ipRefreshBtn.Text = "Use Local"
$ipRefreshBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1)) $ipRefreshBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
$ipRefreshBtn.Size = [System.Drawing.Size]::new(80, 26) $ipRefreshBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($ipRefreshBtn) $panel.Controls.Add($ipRefreshBtn)
Style-ButtonSecondary $ipRefreshBtn Style-ButtonSecondary $ipRefreshBtn
$y += $rowHeight + $gap $y += $rowHeight + $gap
@@ -459,7 +492,7 @@ $y += $rowHeight + $gap
Add-Label "Primary DNS server" $xLabel $y $labelWidth $rowHeight Add-Label "Primary DNS server" $xLabel $y $labelWidth $rowHeight
$dnsServerBox = New-Object System.Windows.Forms.ComboBox $dnsServerBox = New-Object System.Windows.Forms.ComboBox
$dnsServerBox.Location = [System.Drawing.Point]::new($xInput, $y) $dnsServerBox.Location = [System.Drawing.Point]::new($xInput, $y)
$dnsServerBox.Size = [System.Drawing.Size]::new(($inputWidth - 90), $rowHeight) $dnsServerBox.Size = [System.Drawing.Size]::new(($inputWidth - ($buttonWidth + $buttonGap)), $rowHeight)
$dnsServerBox.DropDownStyle = "DropDown" $dnsServerBox.DropDownStyle = "DropDown"
$dnsServerBox.Text = "DC01.example.local" $dnsServerBox.Text = "DC01.example.local"
$dnsServerBox.FlatStyle = "Flat" $dnsServerBox.FlatStyle = "Flat"
@@ -469,7 +502,7 @@ $panel.Controls.Add($dnsServerBox)
$dnsScanBtn = New-Object System.Windows.Forms.Button $dnsScanBtn = New-Object System.Windows.Forms.Button
$dnsScanBtn.Text = "Scan" $dnsScanBtn.Text = "Scan"
$dnsScanBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1)) $dnsScanBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
$dnsScanBtn.Size = [System.Drawing.Size]::new(80, 26) $dnsScanBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($dnsScanBtn) $panel.Controls.Add($dnsScanBtn)
Style-ButtonSecondary $dnsScanBtn Style-ButtonSecondary $dnsScanBtn
$y += $rowHeight + $gap $y += $rowHeight + $gap
@@ -486,17 +519,17 @@ $panel.Controls.Add($dnsListBox)
$y += 82 + $gap $y += 82 + $gap
Add-Label "Replication targets (one per line)" $xLabel $y $labelWidth $rowHeight Add-Label "Replication targets (one per line)" $xLabel $y $labelWidth $rowHeight
$replicationTargetsBox = Add-TextBox $xInput $y ($inputWidth - 180) 70 $true $replicationTargetsBox = Add-TextBox $xInput $y ($inputWidth - ((2 * $buttonWidth) + $buttonGap)) 70 $true
$replicationFromSelectedBtn = New-Object System.Windows.Forms.Button $replicationFromSelectedBtn = New-Object System.Windows.Forms.Button
$replicationFromSelectedBtn.Text = "Use Selected" $replicationFromSelectedBtn.Text = "Use Selected"
$replicationFromSelectedBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - (2 * $buttonWidth + $buttonGap)), ($y - 1)) $replicationFromSelectedBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - (2 * $buttonWidth + $buttonGap)), ($y - 1))
$replicationFromSelectedBtn.Size = [System.Drawing.Size]::new(80, 26) $replicationFromSelectedBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($replicationFromSelectedBtn) $panel.Controls.Add($replicationFromSelectedBtn)
Style-ButtonSecondary $replicationFromSelectedBtn Style-ButtonSecondary $replicationFromSelectedBtn
$primaryFromSelectedBtn = New-Object System.Windows.Forms.Button $primaryFromSelectedBtn = New-Object System.Windows.Forms.Button
$primaryFromSelectedBtn.Text = "Use Primary" $primaryFromSelectedBtn.Text = "Use Primary"
$primaryFromSelectedBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1)) $primaryFromSelectedBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
$primaryFromSelectedBtn.Size = [System.Drawing.Size]::new(80, 26) $primaryFromSelectedBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($primaryFromSelectedBtn) $panel.Controls.Add($primaryFromSelectedBtn)
Style-ButtonSecondary $primaryFromSelectedBtn Style-ButtonSecondary $primaryFromSelectedBtn
$y += 70 + $gap $y += 70 + $gap
@@ -554,6 +587,7 @@ $y += $rowHeight + $gap
Add-SectionHeader "Run" Add-SectionHeader "Run"
$verboseBox = Add-CheckBox "Verbose" $xInput $y 120 $rowHeight $verboseBox = Add-CheckBox "Verbose" $xInput $y 120 $rowHeight
$runWacsBox = Add-CheckBox "Run WACS after DNS update" ($xInput + 140) $y 260 $rowHeight $runWacsBox = Add-CheckBox "Run WACS after DNS update" ($xInput + 140) $y 260 $rowHeight
$perHostBox = Add-CheckBox "One cert per host" ($xInput + 430) $y 180 $rowHeight
$runWacsBox.Checked = $true $runWacsBox.Checked = $true
$y += $rowHeight + ($gap * 2) $y += $rowHeight + ($gap * 2)
@@ -567,10 +601,17 @@ Style-ButtonPrimary $runBtn
$clearBtn = New-Object System.Windows.Forms.Button $clearBtn = New-Object System.Windows.Forms.Button
$clearBtn.Text = "Clear Log" $clearBtn.Text = "Clear Log"
$clearBtn.Location = [System.Drawing.Point]::new(($xInput + 140), $y) $clearBtn.Location = [System.Drawing.Point]::new(($xInput + 140), $y)
$clearBtn.Size = [System.Drawing.Size]::new(120, 30) $clearBtn.Size = [System.Drawing.Size]::new($actionButtonWidth, 30)
$panel.Controls.Add($clearBtn) $panel.Controls.Add($clearBtn)
Style-ButtonSecondary $clearBtn Style-ButtonSecondary $clearBtn
$saveDefaultsBtn = New-Object System.Windows.Forms.Button
$saveDefaultsBtn.Text = "Save Defaults"
$saveDefaultsBtn.Location = [System.Drawing.Point]::new(($xInput + 140 + $actionButtonWidth + $buttonGap), $y)
$saveDefaultsBtn.Size = [System.Drawing.Size]::new($actionButtonWidth, 30)
$panel.Controls.Add($saveDefaultsBtn)
Style-ButtonSecondary $saveDefaultsBtn
$y += 40 $y += 40
Add-SectionHeader "Activity" Add-SectionHeader "Activity"
Add-Label "Activity log" $xLabel $y $labelWidth $rowHeight Add-Label "Activity log" $xLabel $y $labelWidth $rowHeight
@@ -583,6 +624,42 @@ $logAction = {
$logBox.AppendText("[$timestamp] $Message`r`n") $logBox.AppendText("[$timestamp] $Message`r`n")
} }
function Update-OutputTypeUI {
if ($outputTypeBox.SelectedItem -eq "PEM") {
$outputPathLabel.Text = "PEM output path"
$pfxPasswordLabel.Visible = $false
$pfxPasswordBox.Visible = $false
$pfxPasswordBox.Text = ""
} else {
$outputPathLabel.Text = "PFX output path"
$pfxPasswordLabel.Visible = $true
$pfxPasswordBox.Visible = $true
}
}
$loadedDefaults = Load-Defaults
if ($loadedDefaults) {
if ($loadedDefaults.DefaultZone) { $zoneBox.Text = $loadedDefaults.DefaultZone }
if ($loadedDefaults.TargetIp) { $ipBox.Text = $loadedDefaults.TargetIp }
if ($loadedDefaults.DnsServer) { $dnsServerBox.Text = $loadedDefaults.DnsServer }
if ($loadedDefaults.ReplicationTargets) { $replicationTargetsBox.Text = $loadedDefaults.ReplicationTargets }
if ($loadedDefaults.ReplicationCommand) { $replicationCmdBox.Text = $loadedDefaults.ReplicationCommand }
if ($loadedDefaults.WacsPath) { $wacsPathBox.Text = $loadedDefaults.WacsPath }
if ($loadedDefaults.OutputPath) { $outputPathBox.Text = $loadedDefaults.OutputPath }
if ($loadedDefaults.PfxPassword) { $pfxPasswordBox.Text = $loadedDefaults.PfxPassword }
if ($loadedDefaults.BaseUri) { $baseUriBox.Text = $loadedDefaults.BaseUri }
if ($loadedDefaults.Validation) { $validationBox.Text = $loadedDefaults.Validation }
if ($loadedDefaults.ValidationPort) { $validationPortBox.Text = $loadedDefaults.ValidationPort }
if ($null -ne $loadedDefaults.UseProvidedFqdn) { $useFqdnBox.Checked = [bool]$loadedDefaults.UseProvidedFqdn }
if ($null -ne $loadedDefaults.RunWacs) { $runWacsBox.Checked = [bool]$loadedDefaults.RunWacs }
if ($null -ne $loadedDefaults.Verbose) { $verboseBox.Checked = [bool]$loadedDefaults.Verbose }
if ($null -ne $loadedDefaults.PerHostCerts) { $perHostBox.Checked = [bool]$loadedDefaults.PerHostCerts }
if ($loadedDefaults.OutputType) { $outputTypeBox.SelectedItem = $loadedDefaults.OutputType }
if (-not $outputTypeBox.SelectedItem) { $outputTypeBox.SelectedIndex = 0 }
Update-OutputTypeUI
& $logAction "Defaults loaded from $(Get-DefaultsPath)."
}
function Apply-Layout { function Apply-Layout {
if ($panel.ClientSize.Width -le 0) { return } if ($panel.ClientSize.Width -le 0) { return }
$contentWidth = $panel.ClientSize.Width - $leftMargin - $rightMargin $contentWidth = $panel.ClientSize.Width - $leftMargin - $rightMargin
@@ -620,6 +697,8 @@ function Apply-Layout {
$validationBox.Width = $inputWidthCalc $validationBox.Width = $inputWidthCalc
$validationPortBox.Width = $inputWidthCalc $validationPortBox.Width = $inputWidthCalc
$logBox.Width = $inputWidthCalc $logBox.Width = $inputWidthCalc
$clearBtn.Left = $xInput + 140
$saveDefaultsBtn.Left = $clearBtn.Left + $clearBtn.Width + $buttonGap
} }
$browseBtn.Add_Click({ $browseBtn.Add_Click({
@@ -714,15 +793,32 @@ $primaryFromSelectedBtn.Add_Click({
}) })
$outputTypeBox.Add_SelectedIndexChanged({ $outputTypeBox.Add_SelectedIndexChanged({
if ($outputTypeBox.SelectedItem -eq "PEM") { Update-OutputTypeUI
$outputPathLabel.Text = "PEM output path" })
$pfxPasswordLabel.Visible = $false
$pfxPasswordBox.Visible = $false $saveDefaultsBtn.Add_Click({
$pfxPasswordBox.Text = "" $defaults = [pscustomobject]@{
} else { DefaultZone = $zoneBox.Text
$outputPathLabel.Text = "PFX output path" TargetIp = $ipBox.Text
$pfxPasswordLabel.Visible = $true DnsServer = $dnsServerBox.Text
$pfxPasswordBox.Visible = $true ReplicationTargets = $replicationTargetsBox.Text
ReplicationCommand = $replicationCmdBox.Text
WacsPath = $wacsPathBox.Text
OutputType = $outputTypeBox.SelectedItem.ToString()
OutputPath = $outputPathBox.Text
PfxPassword = $pfxPasswordBox.Text
BaseUri = $baseUriBox.Text
Validation = $validationBox.Text
ValidationPort = $validationPortBox.Text
UseProvidedFqdn = $useFqdnBox.Checked
RunWacs = $runWacsBox.Checked
Verbose = $verboseBox.Checked
PerHostCerts = $perHostBox.Checked
}
Save-Defaults -Defaults $defaults
& $logAction "Defaults saved to $(Get-DefaultsPath)."
if (-not [string]::IsNullOrWhiteSpace($pfxPasswordBox.Text)) {
& $logAction "Warning: PFX password is stored in plaintext."
} }
}) })
@@ -785,10 +881,27 @@ $runBtn.Add_Click({
if (-not (Test-Path -Path $wacsPath -PathType Leaf)) { if (-not (Test-Path -Path $wacsPath -PathType Leaf)) {
throw "WACS not found at: $wacsPath" throw "WACS not found at: $wacsPath"
} }
foreach ($entry in $hostEntries) { if ($perHostBox.Checked) {
foreach ($entry in $hostEntries) {
& $logAction "Requesting certificate for $($entry.Fqdn)."
Invoke-Wacs `
-WacsPath $wacsPath `
-HostFqdns @($entry.Fqdn) `
-OutputType $outputType `
-OutputPath $outputPath `
-PfxPassword $pfxPasswordBox.Text `
-BaseUri $baseUriBox.Text.Trim() `
-Validation $validationBox.Text.Trim() `
-ValidationPort $validationPortBox.Text.Trim() `
-Verbose $verboseBox.Checked `
-Log $logAction
}
} else {
$hostList = $hostEntries | ForEach-Object { $_.Fqdn }
& $logAction "Requesting one certificate with $($hostList.Count) hostname(s)."
Invoke-Wacs ` Invoke-Wacs `
-WacsPath $wacsPath ` -WacsPath $wacsPath `
-HostFqdn $entry.Fqdn ` -HostFqdns $hostList `
-OutputType $outputType ` -OutputType $outputType `
-OutputPath $outputPath ` -OutputPath $outputPath `
-PfxPassword $pfxPasswordBox.Text ` -PfxPassword $pfxPasswordBox.Text `