Files
cert-management/certy.ps1

1175 lines
44 KiB
PowerShell

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function Split-List {
param([string]$Text)
if ([string]::IsNullOrWhiteSpace($Text)) { return @() }
return $Text -split '[,\r\n;]+' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
}
function Merge-Hostnames {
param(
[string[]]$Existing,
[string[]]$NewItems
)
$set = New-Object System.Collections.Generic.HashSet[string] ([System.StringComparer]::OrdinalIgnoreCase)
foreach ($item in $Existing) { [void]$set.Add($item) }
foreach ($item in $NewItems) { [void]$set.Add($item) }
return $set | Sort-Object
}
function Get-LocalIpv4 {
try {
$candidates = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction Stop |
Where-Object {
$_.IPAddress -and
$_.IPAddress -notlike "169.254.*" -and
$_.IPAddress -ne "127.0.0.1" -and
$_.PrefixOrigin -in @("Dhcp", "Manual")
} |
Sort-Object -Property InterfaceMetric, SkipAsSource
$ip = $candidates | Select-Object -First 1 -ExpandProperty IPAddress
if ($ip) { return $ip }
} catch {
# Ignore and fallback to DNS lookup.
}
try {
$dnsIps = [System.Net.Dns]::GetHostAddresses($env:COMPUTERNAME) |
Where-Object {
$_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork -and
$_.ToString() -notlike "169.254.*" -and
$_.ToString() -ne "127.0.0.1"
} |
Select-Object -First 1
if ($dnsIps) { return $dnsIps.ToString() }
} catch {
# Ignore.
}
return ""
}
function Get-FilePreview {
param(
[string]$Path,
[int]$MaxLines = 200
)
if ([string]::IsNullOrWhiteSpace($Path)) { return "" }
if (-not (Test-Path -Path $Path -PathType Leaf)) { return "" }
try {
$lines = Get-Content -Path $Path -TotalCount $MaxLines -ErrorAction Stop
return ($lines -join [Environment]::NewLine)
} catch {
return "Failed to read file preview: $($_.Exception.Message)"
}
}
function Get-CommonZoneFromHosts {
param([string[]]$Hosts)
$fqdnHosts = @($Hosts | Where-Object { $_ -and $_.Contains(".") })
if (-not $fqdnHosts -or $fqdnHosts.Count -eq 0) { return "" }
$commonSuffix = $null
foreach ($hostName in $fqdnHosts) {
$clean = $hostName.Trim().TrimEnd(".")
$labels = $clean -split "\."
if ($labels.Count -lt 2) { continue }
$zoneLabels = $labels[1..($labels.Count - 1)]
if (-not $commonSuffix) {
$commonSuffix = $zoneLabels
continue
}
$i = $commonSuffix.Count - 1
$j = $zoneLabels.Count - 1
$newSuffix = @()
while ($i -ge 0 -and $j -ge 0) {
if ($commonSuffix[$i].ToLower() -ne $zoneLabels[$j].ToLower()) { break }
$newSuffix = ,$zoneLabels[$j] + $newSuffix
$i--
$j--
}
$commonSuffix = $newSuffix
if (-not $commonSuffix -or $commonSuffix.Count -eq 0) { break }
}
if (-not $commonSuffix -or $commonSuffix.Count -eq 0) { return "" }
return ($commonSuffix -join ".")
}
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 Get-DefaultValue {
param(
[object]$Defaults,
[string]$Name
)
if ($null -eq $Defaults) { return $null }
$prop = $Defaults.PSObject.Properties[$Name]
if ($null -eq $prop) { return $null }
return $prop.Value
}
function Resolve-HostEntry {
param(
[string]$Name,
[string]$Zone,
[bool]$UseProvidedFqdn
)
$name = $Name.Trim()
if (-not $name) { return $null }
$zoneLower = $Zone.ToLower()
$nameLower = $name.ToLower()
if ($UseProvidedFqdn) {
if ($name -like "*.*") { $fqdn = $name } else { $fqdn = "$name.$Zone" }
} else {
if ($name -like "*.*") {
if ($nameLower.EndsWith(".$zoneLower") -or $nameLower -eq $zoneLower) {
$fqdn = $name
} else {
$fqdn = "$name.$Zone"
}
} else {
$fqdn = "$name.$Zone"
}
}
$fqdnLower = $fqdn.ToLower()
if ($fqdnLower.EndsWith(".$zoneLower")) {
$hostLabel = $fqdn.Substring(0, $fqdn.Length - $Zone.Length - 1)
} elseif ($fqdnLower -eq $zoneLower) {
$hostLabel = "@"
} else {
$hostLabel = $fqdn
}
return [pscustomobject]@{
Input = $name
Fqdn = $fqdn
HostLabel = $hostLabel
}
}
function Get-DnsServerCandidates {
$servers = @()
try {
Import-Module ActiveDirectory -ErrorAction Stop | Out-Null
$servers += Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName
} catch {
# Ignore AD lookup failures and fallback to local DNS client config.
}
try {
$local = Get-DnsClientServerAddress -AddressFamily IPv4 -ErrorAction Stop |
ForEach-Object { $_.ServerAddresses } | Where-Object { $_ }
$servers += $local
} catch {
# Ignore if DnsClient module is unavailable.
}
return $servers | Sort-Object -Unique
}
function Ensure-ARecord {
param(
[string]$Zone,
[string]$HostLabel,
[string]$TargetIp,
[string]$DnsServer,
[scriptblock]$Log
)
$existing = Get-DnsServerResourceRecord -ZoneName $Zone -Name $HostLabel -RRType "A" -ComputerName $DnsServer -ErrorAction SilentlyContinue
if ($existing) {
$record = $existing | Select-Object -First 1
$currentIp = $record.RecordData.IPv4Address.ToString()
if ($currentIp -eq $TargetIp) {
& $Log "DNS A record exists: $HostLabel.$Zone -> $TargetIp"
return
}
$newRecord = $record.Clone()
$newRecord.RecordData.IPv4Address = [ipaddress]$TargetIp
Set-DnsServerResourceRecord -ZoneName $Zone -OldInputObject $record -NewInputObject $newRecord -ComputerName $DnsServer
& $Log "DNS A record updated: $HostLabel.$Zone -> $TargetIp"
return
}
Add-DnsServerResourceRecordA -Name $HostLabel -ZoneName $Zone -IPv4Address $TargetIp -ComputerName $DnsServer
& $Log "DNS A record added: $HostLabel.$Zone -> $TargetIp"
}
function Invoke-Replication {
param(
[string[]]$Servers,
[string]$Command,
[bool]$UseRemote,
[scriptblock]$Log
)
if ([string]::IsNullOrWhiteSpace($Command)) { return }
$targets = @($Servers | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
if ($targets.Count -eq 0) {
& $Log "Replication skipped: no target servers."
return
}
foreach ($server in $targets) {
$usesToken = $Command -match "\{server\}"
$cmd = if ($usesToken) { $Command.Replace("{server}", $server) } else { $Command }
$cmd = $cmd.Trim()
if ($cmd -match "^(?i)\s*/repadmin\b") {
$cmd = $cmd -replace "^(?i)\s*/repadmin\b", "repadmin"
}
if ($UseRemote -and $cmd -match "(?i)\brepadmin\b" -and $cmd -match "(?i)\bsyncall\b") {
$remoteCmd = [regex]::Replace($cmd, "(?i)\\brepadmin\\s+/syncall\\s+\\S+", "repadmin /syncall")
& $Log "Replication (remote): $server -> $remoteCmd"
try {
Invoke-Command -ComputerName $server -ScriptBlock { param($c) & $env:ComSpec /c $c } -ArgumentList $remoteCmd |
ForEach-Object { & $Log $_ }
} catch {
& $Log ("Replication error on {0}: {1}" -f $server, $_.Exception.Message)
}
continue
}
if (-not $usesToken -and $server -and $cmd -match "(?i)\brepadmin\b" -and $cmd -match "(?i)\bsyncall\b") {
if ($cmd -notmatch "(?i)\\bsyncall\\s+\\S+") {
$cmd = $cmd -replace "(?i)\\bsyncall\\b", "syncall $server"
}
}
if ([string]::IsNullOrWhiteSpace($cmd)) { continue }
& $Log "Replication: $cmd"
& $env:ComSpec /c $cmd | ForEach-Object { & $Log $_ }
}
}
function Invoke-Wacs {
param(
[string]$WacsPath,
[string[]]$HostFqdns,
[string]$OutputType,
[string]$OutputPath,
[string]$PfxPassword,
[string]$BaseUri,
[string]$Validation,
[string]$ValidationPort,
[bool]$Verbose,
[scriptblock]$Log
)
$args = @("--target", "manual")
foreach ($hostName in $HostFqdns) {
if (-not [string]::IsNullOrWhiteSpace($hostName)) {
$args += @("--host", $hostName)
}
}
if ($OutputType -eq "PEM") {
$args += @("--store", "pemfiles", "--pemfilespath", $OutputPath)
} else {
$args += @("--store", "pfxfile", "--pfxfilepath", $OutputPath)
if (-not [string]::IsNullOrWhiteSpace($PfxPassword)) {
$args += @("--pfxpassword", $PfxPassword)
}
}
$args += @(
"--baseuri", $BaseUri,
"--validation", $Validation,
"--validationport", $ValidationPort
)
if ($Verbose) { $args += "--verbose" }
& $Log "WACS: $WacsPath $($args -join ' ')"
& $WacsPath @args
}
$form = New-Object System.Windows.Forms.Form
$form.Text = "Certy - WACS Helper"
$form.Size = [System.Drawing.Size]::new(1000, 860)
$form.StartPosition = "CenterScreen"
$form.AutoScaleMode = "Dpi"
$colorBg = [System.Drawing.Color]::FromArgb(245, 246, 248)
$colorPanel = [System.Drawing.Color]::FromArgb(255, 255, 255)
$colorText = [System.Drawing.Color]::FromArgb(30, 37, 45)
$colorMuted = [System.Drawing.Color]::FromArgb(90, 98, 110)
$colorAccent = [System.Drawing.Color]::FromArgb(32, 46, 77)
$colorAccentSoft = [System.Drawing.Color]::FromArgb(41, 58, 96)
$colorBorder = [System.Drawing.Color]::FromArgb(220, 224, 230)
$colorInput = [System.Drawing.Color]::FromArgb(255, 255, 255)
$form.BackColor = $colorBg
$sidebarWidth = 170
$sidebar = New-Object System.Windows.Forms.Panel
$sidebar.Dock = "Left"
$sidebar.Width = $sidebarWidth
$sidebar.BackColor = $colorAccent
$panel = New-Object System.Windows.Forms.Panel
$panel.Dock = "Fill"
$panel.AutoScroll = $true
$panel.BackColor = $colorBg
$form.Controls.Add($panel)
$form.Controls.Add($sidebar)
$font = New-Object System.Drawing.Font("Segoe UI", 9)
$labelWidth = 200
$inputWidth = 720
$xLabel = 20
$xInput = 230
$y = 20
$rowHeight = 24
$gap = 8
$leftMargin = 20
$rightMargin = 20
$buttonWidth = 110
$buttonGap = 10
$actionButtonWidth = 130
$navTitle = New-Object System.Windows.Forms.Label
$navTitle.Text = "CERTY"
$navTitle.Font = New-Object System.Drawing.Font("Segoe UI Semibold", 12)
$navTitle.ForeColor = [System.Drawing.Color]::White
$navTitle.Location = [System.Drawing.Point]::new(16, 16)
$navTitle.Size = [System.Drawing.Size]::new(130, 24)
$sidebar.Controls.Add($navTitle)
$navSub = New-Object System.Windows.Forms.Label
$navSub.Text = "Enterprise Console"
$navSub.Font = New-Object System.Drawing.Font("Segoe UI", 8)
$navSub.ForeColor = [System.Drawing.Color]::FromArgb(200, 214, 240)
$navSub.Location = [System.Drawing.Point]::new(16, 38)
$navSub.Size = [System.Drawing.Size]::new(140, 18)
$sidebar.Controls.Add($navSub)
$navItems = @("Input", "DNS", "ACME", "Run", "Logs")
$navY = 80
foreach ($item in $navItems) {
$navLabel = New-Object System.Windows.Forms.Label
$navLabel.Text = $item
$navLabel.Font = New-Object System.Drawing.Font("Segoe UI Semibold", 9)
$navLabel.ForeColor = [System.Drawing.Color]::FromArgb(210, 220, 236)
$navLabel.Location = [System.Drawing.Point]::new(16, $navY)
$navLabel.Size = [System.Drawing.Size]::new(140, 20)
$sidebar.Controls.Add($navLabel)
$navY += 26
}
function Style-ButtonPrimary {
param([System.Windows.Forms.Button]$Button)
$Button.BackColor = $colorAccent
$Button.ForeColor = [System.Drawing.Color]::White
$Button.FlatStyle = "Flat"
$Button.FlatAppearance.BorderSize = 0
$Button.UseVisualStyleBackColor = $false
}
function Style-ButtonSecondary {
param([System.Windows.Forms.Button]$Button)
$Button.BackColor = $colorPanel
$Button.ForeColor = $colorText
$Button.FlatStyle = "Flat"
$Button.FlatAppearance.BorderColor = $colorBorder
$Button.FlatAppearance.BorderSize = 1
$Button.UseVisualStyleBackColor = $false
}
function Add-Label {
param([string]$Text, [int]$X, [int]$Y, [int]$W, [int]$H)
$label = New-Object System.Windows.Forms.Label
$label.Text = $Text
$label.Location = [System.Drawing.Point]::new($X, $Y)
$label.Size = [System.Drawing.Size]::new($W, $H)
$label.Font = $font
$label.ForeColor = $colorMuted
$panel.Controls.Add($label)
return $label
}
function Add-TextBox {
param([int]$X, [int]$Y, [int]$W, [int]$H, [bool]$Multiline = $false)
$tb = New-Object System.Windows.Forms.TextBox
$tb.Location = [System.Drawing.Point]::new($X, $Y)
$tb.Size = [System.Drawing.Size]::new($W, $H)
$tb.Font = $font
$tb.BackColor = $colorInput
$tb.ForeColor = $colorText
$tb.BorderStyle = "FixedSingle"
$tb.Multiline = $Multiline
if ($Multiline) {
$tb.ScrollBars = "Vertical"
}
$panel.Controls.Add($tb)
return $tb
}
function Add-CheckBox {
param([string]$Text, [int]$X, [int]$Y, [int]$W, [int]$H)
$cb = New-Object System.Windows.Forms.CheckBox
$cb.Text = $Text
$cb.Location = [System.Drawing.Point]::new($X, $Y)
$cb.Size = [System.Drawing.Size]::new($W, $H)
$cb.Font = $font
$cb.ForeColor = $colorText
$panel.Controls.Add($cb)
return $cb
}
function Add-SectionHeader {
param([string]$Text)
$sectionLabel = New-Object System.Windows.Forms.Label
$sectionLabel.Text = $Text
$sectionLabel.Font = New-Object System.Drawing.Font("Segoe UI Semibold", 10)
$sectionLabel.ForeColor = $colorText
$sectionLabel.Location = [System.Drawing.Point]::new($xLabel, $script:y)
$sectionLabel.Size = [System.Drawing.Size]::new(300, 20)
$panel.Controls.Add($sectionLabel)
$sectionLine = New-Object System.Windows.Forms.Panel
$sectionLine.BackColor = $colorBorder
$sectionLine.Location = [System.Drawing.Point]::new($xLabel, ($script:y + 22))
$sectionLine.Size = [System.Drawing.Size]::new(($inputWidth + ($xInput - $xLabel)), 1)
$panel.Controls.Add($sectionLine)
$script:sectionLines.Add($sectionLine) | Out-Null
$script:y += 30
}
$sectionLines = New-Object System.Collections.Generic.List[System.Windows.Forms.Panel]
$header = New-Object System.Windows.Forms.Panel
$header.Location = [System.Drawing.Point]::new($xLabel, $y)
$header.Size = [System.Drawing.Size]::new(($inputWidth + ($xInput - $xLabel)), 70)
$header.BackColor = $colorPanel
$header.BorderStyle = "FixedSingle"
$panel.Controls.Add($header)
$headerTitle = New-Object System.Windows.Forms.Label
$headerTitle.Text = "Certy Enterprise"
$headerTitle.Font = New-Object System.Drawing.Font("Segoe UI Semibold", 16)
$headerTitle.ForeColor = $colorText
$headerTitle.Location = [System.Drawing.Point]::new(12, 10)
$headerTitle.Size = [System.Drawing.Size]::new(300, 28)
$header.Controls.Add($headerTitle)
$headerSub = New-Object System.Windows.Forms.Label
$headerSub.Text = "WACS helper for DNS + ACME proxy workflows"
$headerSub.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$headerSub.ForeColor = $colorMuted
$headerSub.Location = [System.Drawing.Point]::new(12, 38)
$headerSub.Size = [System.Drawing.Size]::new(600, 20)
$header.Controls.Add($headerSub)
$y = $header.Bottom + 16
Add-SectionHeader "Input"
Add-Label "Hostnames (one per line)" $xLabel $y $labelWidth $rowHeight
$hostsBox = Add-TextBox $xInput $y $inputWidth 100 $true
$y += 100 + $gap
Add-Label "Hostnames file (optional)" $xLabel $y $labelWidth $rowHeight
$fileBox = Add-TextBox $xInput $y ($inputWidth - ($buttonWidth + $buttonGap)) $rowHeight $false
$browseBtn = New-Object System.Windows.Forms.Button
$browseBtn.Text = "Browse"
$browseBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
$browseBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($browseBtn)
Style-ButtonSecondary $browseBtn
$y += $rowHeight + $gap
Add-Label "File preview (first 200 lines)" $xLabel $y $labelWidth $rowHeight
$filePreviewBox = Add-TextBox $xInput $y ($inputWidth - ($buttonWidth + $buttonGap)) 80 $true
$filePreviewBox.ReadOnly = $true
$filePreviewBtn = New-Object System.Windows.Forms.Button
$filePreviewBtn.Text = "Preview"
$filePreviewBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
$filePreviewBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($filePreviewBtn)
Style-ButtonSecondary $filePreviewBtn
$y += 82 + $gap
$csrLabel = Add-Label "CSR folder (optional)" $xLabel $y $labelWidth $rowHeight
$csrFolderBox = Add-TextBox $xInput $y ($inputWidth - ((2 * $buttonWidth) + $buttonGap)) $rowHeight $false
$csrBrowseBtn = New-Object System.Windows.Forms.Button
$csrBrowseBtn.Text = "Browse"
$csrBrowseBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - (2 * $buttonWidth + $buttonGap)), ($y - 1))
$csrBrowseBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($csrBrowseBtn)
Style-ButtonSecondary $csrBrowseBtn
$csrImportBtn = New-Object System.Windows.Forms.Button
$csrImportBtn.Text = "Import CSR"
$csrImportBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
$csrImportBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($csrImportBtn)
Style-ButtonSecondary $csrImportBtn
$y += $rowHeight + $gap
$useFqdnBox = Add-CheckBox "Input contains FQDNs (otherwise default zone is appended)" $xInput $y $inputWidth $rowHeight
$y += $rowHeight + $gap
Add-SectionHeader "DNS & Replication"
Add-Label "Default DNS zone" $xLabel $y $labelWidth $rowHeight
$zoneBox = Add-TextBox $xInput $y $inputWidth $rowHeight $false
$zoneBox.Text = "record.domain.govt.nz"
$y += $rowHeight + $gap
$replicationEnabledBox = Add-CheckBox "Enable DNS replication" $xInput $y 200 $rowHeight
$replicationEnabledBox.Checked = $true
$y += $rowHeight + $gap
Add-Label "Target IPv4 for A records" $xLabel $y $labelWidth $rowHeight
$ipBox = Add-TextBox $xInput $y ($inputWidth - ($buttonWidth + $buttonGap)) $rowHeight $false
$ipBox.Text = Get-LocalIpv4
$ipRefreshBtn = New-Object System.Windows.Forms.Button
$ipRefreshBtn.Text = "Use Local"
$ipRefreshBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
$ipRefreshBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($ipRefreshBtn)
Style-ButtonSecondary $ipRefreshBtn
$y += $rowHeight + $gap
Add-Label "Primary DNS server" $xLabel $y $labelWidth $rowHeight
$dnsServerBox = New-Object System.Windows.Forms.ComboBox
$dnsServerBox.Location = [System.Drawing.Point]::new($xInput, $y)
$dnsServerBox.Size = [System.Drawing.Size]::new(($inputWidth - ($buttonWidth + $buttonGap)), $rowHeight)
$dnsServerBox.DropDownStyle = "DropDown"
$dnsServerBox.Text = "DC01.example.local"
$dnsServerBox.FlatStyle = "Flat"
$dnsServerBox.BackColor = $colorInput
$dnsServerBox.ForeColor = $colorText
$panel.Controls.Add($dnsServerBox)
$dnsScanBtn = New-Object System.Windows.Forms.Button
$dnsScanBtn.Text = "Scan"
$dnsScanBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
$dnsScanBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($dnsScanBtn)
Style-ButtonSecondary $dnsScanBtn
$y += $rowHeight + $gap
Add-Label "DNS servers (select for replication)" $xLabel $y $labelWidth $rowHeight
$dnsListBox = New-Object System.Windows.Forms.ListBox
$dnsListBox.Location = [System.Drawing.Point]::new($xInput, $y)
$dnsListBox.Size = [System.Drawing.Size]::new($inputWidth, 80)
$dnsListBox.SelectionMode = "MultiExtended"
$dnsListBox.BackColor = $colorInput
$dnsListBox.ForeColor = $colorText
$dnsListBox.BorderStyle = "FixedSingle"
$panel.Controls.Add($dnsListBox)
$y += 82 + $gap
Add-Label "Replication targets (one per line)" $xLabel $y $labelWidth $rowHeight
$replicationTargetsBox = Add-TextBox $xInput $y ($inputWidth - ((2 * $buttonWidth) + $buttonGap)) 70 $true
$replicationFromSelectedBtn = New-Object System.Windows.Forms.Button
$replicationFromSelectedBtn.Text = "Use Selected"
$replicationFromSelectedBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - (2 * $buttonWidth + $buttonGap)), ($y - 1))
$replicationFromSelectedBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($replicationFromSelectedBtn)
Style-ButtonSecondary $replicationFromSelectedBtn
$primaryFromSelectedBtn = New-Object System.Windows.Forms.Button
$primaryFromSelectedBtn.Text = "Use Primary"
$primaryFromSelectedBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
$primaryFromSelectedBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($primaryFromSelectedBtn)
Style-ButtonSecondary $primaryFromSelectedBtn
$y += 70 + $gap
Add-Label "Replication command ({server} optional)" $xLabel $y $labelWidth $rowHeight
$replicationCmdBox = Add-TextBox $xInput $y $inputWidth $rowHeight $false
$replicationCmdBox.Text = "repadmin /syncall {server} /AdeP"
$y += $rowHeight + $gap
Add-Label "Replication wait (seconds)" $xLabel $y $labelWidth $rowHeight
$replicationDelayBox = Add-TextBox $xInput $y $inputWidth $rowHeight $false
$replicationDelayBox.Text = "30"
$y += $rowHeight + $gap
$replicationRemoteBox = Add-CheckBox "Run repadmin remotely (PowerShell)" $xInput $y $inputWidth $rowHeight
$replicationRemoteBox.Checked = $true
$y += $rowHeight + $gap
$y += $gap
Add-SectionHeader "ACME / Output"
Add-Label "WACS path" $xLabel $y $labelWidth $rowHeight
$wacsPathBox = Add-TextBox $xInput $y $inputWidth $rowHeight $false
$wacsPathBox.Text = "C:\ProgramData\Wacs\wacs.exe"
$y += $rowHeight + $gap
Add-Label "Output type" $xLabel $y $labelWidth $rowHeight
$outputTypeBox = New-Object System.Windows.Forms.ComboBox
$outputTypeBox.Location = [System.Drawing.Point]::new($xInput, $y)
$outputTypeBox.Size = [System.Drawing.Size]::new($inputWidth, $rowHeight)
$outputTypeBox.DropDownStyle = "DropDownList"
$outputTypeBox.FlatStyle = "Flat"
$outputTypeBox.BackColor = $colorInput
$outputTypeBox.ForeColor = $colorText
[void]$outputTypeBox.Items.Add("PFX")
[void]$outputTypeBox.Items.Add("PEM")
$outputTypeBox.SelectedIndex = 0
$panel.Controls.Add($outputTypeBox)
$y += $rowHeight + $gap
$outputPathLabel = Add-Label "PFX output path" $xLabel $y $labelWidth $rowHeight
$outputPathBox = Add-TextBox $xInput $y $inputWidth $rowHeight $false
$outputPathBox.Text = "C:\programdata\wacs\output\"
$y += $rowHeight + $gap
$pfxPasswordLabel = Add-Label "PFX password" $xLabel $y $labelWidth $rowHeight
$pfxPasswordBox = Add-TextBox $xInput $y $inputWidth $rowHeight $false
$pfxPasswordBox.UseSystemPasswordChar = $true
$y += $rowHeight + $gap
Add-Label "ACME base URI" $xLabel $y $labelWidth $rowHeight
$baseUriBox = Add-TextBox $xInput $y $inputWidth $rowHeight $false
$baseUriBox.Text = "https://acmeprod.wd.govt.nz:9999/acme/rsa/"
$y += $rowHeight + $gap
Add-Label "Validation method" $xLabel $y $labelWidth $rowHeight
$validationBox = Add-TextBox $xInput $y $inputWidth $rowHeight $false
$validationBox.Text = "selfhosting"
$y += $rowHeight + $gap
Add-Label "Validation port" $xLabel $y $labelWidth $rowHeight
$validationPortBox = Add-TextBox $xInput $y $inputWidth $rowHeight $false
$validationPortBox.Text = "9998"
$y += $rowHeight + $gap
Add-SectionHeader "Run"
$verboseBox = Add-CheckBox "Verbose" $xInput $y 120 $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
$y += $rowHeight + $gap
$disableCertsBox = Add-CheckBox "Turn off cert generation (DNS-only mode)" $xInput $y 360 $rowHeight
$y += $rowHeight + ($gap * 2)
$runBtn = New-Object System.Windows.Forms.Button
$runBtn.Text = "Run"
$runBtn.Location = [System.Drawing.Point]::new($xInput, $y)
$runBtn.Size = [System.Drawing.Size]::new(120, 30)
$panel.Controls.Add($runBtn)
Style-ButtonPrimary $runBtn
$clearBtn = New-Object System.Windows.Forms.Button
$clearBtn.Text = "Clear Log"
$clearBtn.Location = [System.Drawing.Point]::new(($xInput + 140), $y)
$clearBtn.Size = [System.Drawing.Size]::new($actionButtonWidth, 30)
$panel.Controls.Add($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
Add-SectionHeader "Activity"
Add-Label "Activity log" $xLabel $y $labelWidth $rowHeight
$logBox = Add-TextBox $xInput $y $inputWidth 200 $true
$logBox.ReadOnly = $true
$logAction = {
param([string]$Message)
$timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
$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
}
}
function Update-ReplicationUI {
$enabled = $replicationEnabledBox.Checked
$replicationTargetsBox.Enabled = $enabled
$replicationFromSelectedBtn.Enabled = $enabled
$primaryFromSelectedBtn.Enabled = $enabled
$replicationCmdBox.Enabled = $enabled
$replicationDelayBox.Enabled = $enabled
$replicationRemoteBox.Enabled = $enabled
$dnsListBox.Enabled = $enabled
}
function Update-CertGenerationUI {
$disabled = $disableCertsBox.Checked
$runWacsBox.Enabled = -not $disabled
$perHostBox.Enabled = -not $disabled
if ($disabled) {
$runWacsBox.Checked = $false
}
$wacsPathBox.Enabled = -not $disabled
$outputTypeBox.Enabled = -not $disabled
$outputPathBox.Enabled = -not $disabled
$pfxPasswordBox.Enabled = -not $disabled
$baseUriBox.Enabled = -not $disabled
$validationBox.Enabled = -not $disabled
$validationPortBox.Enabled = -not $disabled
}
function Update-ZoneFromHostInput {
$hostList = @(Split-List $hostsBox.Text)
if ($hostList.Count -eq 0) { return }
$zoneGuess = Get-CommonZoneFromHosts -Hosts $hostList
if (-not [string]::IsNullOrWhiteSpace($zoneGuess) -and $zoneBox.Text -ne $zoneGuess) {
$zoneBox.Text = $zoneGuess
& $logAction "Default DNS zone set to $zoneGuess (from hostnames)."
}
}
$loadedDefaults = Load-Defaults
if ($loadedDefaults) {
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "DefaultZone"
if (-not [string]::IsNullOrWhiteSpace($value)) { $zoneBox.Text = $value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "TargetIp"
if (-not [string]::IsNullOrWhiteSpace($value)) { $ipBox.Text = $value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "DnsServer"
if (-not [string]::IsNullOrWhiteSpace($value)) { $dnsServerBox.Text = $value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "ReplicationTargets"
if (-not [string]::IsNullOrWhiteSpace($value)) { $replicationTargetsBox.Text = $value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "ReplicationCommand"
if (-not [string]::IsNullOrWhiteSpace($value)) { $replicationCmdBox.Text = $value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "ReplicationEnabled"
if ($null -ne $value) { $replicationEnabledBox.Checked = [bool]$value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "ReplicationDelaySeconds"
if ($null -ne $value) { $replicationDelayBox.Text = $value.ToString() }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "ReplicationRemote"
if ($null -ne $value) { $replicationRemoteBox.Checked = [bool]$value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "WacsPath"
if (-not [string]::IsNullOrWhiteSpace($value)) { $wacsPathBox.Text = $value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "OutputPath"
if (-not [string]::IsNullOrWhiteSpace($value)) { $outputPathBox.Text = $value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "PfxPassword"
if (-not [string]::IsNullOrWhiteSpace($value)) { $pfxPasswordBox.Text = $value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "BaseUri"
if (-not [string]::IsNullOrWhiteSpace($value)) { $baseUriBox.Text = $value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "Validation"
if (-not [string]::IsNullOrWhiteSpace($value)) { $validationBox.Text = $value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "ValidationPort"
if (-not [string]::IsNullOrWhiteSpace($value)) { $validationPortBox.Text = $value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "UseProvidedFqdn"
if ($null -ne $value) { $useFqdnBox.Checked = [bool]$value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "RunWacs"
if ($null -ne $value) { $runWacsBox.Checked = [bool]$value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "Verbose"
if ($null -ne $value) { $verboseBox.Checked = [bool]$value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "PerHostCerts"
if ($null -ne $value) { $perHostBox.Checked = [bool]$value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "DisableCertGeneration"
if ($null -ne $value) { $disableCertsBox.Checked = [bool]$value }
$value = Get-DefaultValue -Defaults $loadedDefaults -Name "OutputType"
if (-not [string]::IsNullOrWhiteSpace($value)) { $outputTypeBox.SelectedItem = $value }
if (-not $outputTypeBox.SelectedItem) { $outputTypeBox.SelectedIndex = 0 }
Update-OutputTypeUI
if (Test-Path function:Update-ReplicationUI) { Update-ReplicationUI }
Update-CertGenerationUI
& $logAction "Defaults loaded from $(Get-DefaultsPath)."
}
function Apply-Layout {
if ($panel.ClientSize.Width -le 0) { return }
$contentWidth = $panel.ClientSize.Width - $leftMargin - $rightMargin
$inputWidthCalc = [Math]::Max(420, ($contentWidth - ($xInput - $xLabel)))
$script:inputWidth = $inputWidthCalc
$header.Width = $contentWidth
foreach ($line in $sectionLines) {
$line.Width = $contentWidth
}
$hostsBox.Width = $inputWidthCalc
$fileBox.Width = $inputWidthCalc - ($buttonWidth + $buttonGap)
$browseBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
$filePreviewBox.Width = $inputWidthCalc - ($buttonWidth + $buttonGap)
$filePreviewBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
$csrFolderBox.Width = $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
$csrBrowseBtn.Left = $xInput + $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
$csrImportBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
$zoneBox.Width = $inputWidthCalc
$ipBox.Width = $inputWidthCalc - ($buttonWidth + $buttonGap)
$ipRefreshBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
$dnsServerBox.Width = $inputWidthCalc - ($buttonWidth + $buttonGap)
$dnsScanBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
$dnsListBox.Width = $inputWidthCalc
$replicationTargetsBox.Width = $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
$replicationFromSelectedBtn.Left = $xInput + $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
$primaryFromSelectedBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
$replicationCmdBox.Width = $inputWidthCalc
$replicationDelayBox.Width = $inputWidthCalc
$replicationRemoteBox.Width = $inputWidthCalc
$wacsPathBox.Width = $inputWidthCalc
$outputTypeBox.Width = $inputWidthCalc
$outputPathBox.Width = $inputWidthCalc
$pfxPasswordBox.Width = $inputWidthCalc
$baseUriBox.Width = $inputWidthCalc
$validationBox.Width = $inputWidthCalc
$validationPortBox.Width = $inputWidthCalc
$logBox.Width = $inputWidthCalc
$clearBtn.Left = $xInput + 140
$saveDefaultsBtn.Left = $clearBtn.Left + $clearBtn.Width + $buttonGap
}
$browseBtn.Add_Click({
$dialog = New-Object System.Windows.Forms.OpenFileDialog
$dialog.Filter = "Text/CSV files (*.txt;*.csv)|*.txt;*.csv|All files (*.*)|*.*"
$dialog.Multiselect = $false
if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
$fileBox.Text = $dialog.FileName
$filePreviewBox.Text = Get-FilePreview -Path $dialog.FileName
$fileHosts = Get-Content -Path $dialog.FileName | ForEach-Object { $_.Trim() } | Where-Object { $_ }
$zoneGuess = Get-CommonZoneFromHosts -Hosts $fileHosts
if ($zoneGuess) {
$zoneBox.Text = $zoneGuess
& $logAction "Default DNS zone set to $zoneGuess (from file)."
}
}
})
$replicationEnabledBox.Add_CheckedChanged({
if (Test-Path function:Update-ReplicationUI) { Update-ReplicationUI }
})
$filePreviewBtn.Add_Click({
$path = $fileBox.Text.Trim()
if ([string]::IsNullOrWhiteSpace($path)) {
$previewHosts = @(Split-List $hostsBox.Text)
if ($previewHosts.Count -gt 0) {
$filePreviewBox.Text = ($previewHosts | Select-Object -First 200) -join [Environment]::NewLine
return
}
$filePreviewBox.Text = ""
& $logAction "No file selected and no hostnames to preview."
return
}
$filePreviewBox.Text = Get-FilePreview -Path $path
if (Test-Path -Path $path -PathType Leaf) {
$fileHosts = Get-Content -Path $path | ForEach-Object { $_.Trim() } | Where-Object { $_ }
$zoneGuess = Get-CommonZoneFromHosts -Hosts $fileHosts
if ($zoneGuess) {
$zoneBox.Text = $zoneGuess
& $logAction "Default DNS zone set to $zoneGuess (from file)."
}
}
})
$csrBrowseBtn.Add_Click({
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
$csrFolderBox.Text = $dialog.SelectedPath
}
})
$csrImportBtn.Add_Click({
try {
$folder = $csrFolderBox.Text.Trim()
if (-not $folder) { throw "CSR folder is empty." }
if (-not (Test-Path -Path $folder -PathType Container)) { throw "CSR folder not found: $folder" }
$csrFiles = Get-ChildItem -Path $folder -Include *.csr, *.pem -File -Recurse
if (-not $csrFiles) {
& $logAction "No CSR files found in $folder"
return
}
$csrHosts = @($csrFiles | ForEach-Object { $_.BaseName.Trim() } | Where-Object { $_ })
$currentHosts = Split-List $hostsBox.Text
$merged = Merge-Hostnames -Existing $currentHosts -NewItems $csrHosts
$hostsBox.Text = ($merged -join [Environment]::NewLine)
& $logAction "Imported $($csrHosts.Count) CSR hostname(s)."
} catch {
& $logAction "Error: $($_.Exception.Message)"
}
})
$dnsScanBtn.Add_Click({
try {
$dnsListBox.Items.Clear()
$servers = @(Get-DnsServerCandidates)
if (-not $servers -or $servers.Count -eq 0) {
& $logAction "No DNS servers found. Enter servers manually."
return
}
foreach ($server in $servers) {
[void]$dnsListBox.Items.Add($server)
}
$dnsServerBox.Items.Clear()
foreach ($server in $servers) {
[void]$dnsServerBox.Items.Add($server)
}
if ($dnsServerBox.Items.Count -gt 0) {
$dnsServerBox.SelectedIndex = 0
}
& $logAction "Loaded $($servers.Count) DNS server(s)."
} catch {
& $logAction "Error: $($_.Exception.Message)"
}
})
$ipRefreshBtn.Add_Click({
$ipBox.Text = Get-LocalIpv4
})
$replicationFromSelectedBtn.Add_Click({
$selected = @($dnsListBox.SelectedItems | ForEach-Object { $_.ToString() })
if ($selected.Count -eq 0) {
& $logAction "No DNS servers selected."
return
}
$replicationTargetsBox.Text = ($selected -join [Environment]::NewLine)
& $logAction "Replication targets set from selected DNS servers."
})
$primaryFromSelectedBtn.Add_Click({
$selected = @($dnsListBox.SelectedItems | ForEach-Object { $_.ToString() })
if ($selected.Count -eq 0) {
& $logAction "No DNS servers selected."
return
}
$dnsServerBox.Text = $selected[0]
& $logAction "Primary DNS server set to $($selected[0])."
})
$outputTypeBox.Add_SelectedIndexChanged({
Update-OutputTypeUI
})
$hostsBox.Add_TextChanged({
Update-ZoneFromHostInput
})
$disableCertsBox.Add_CheckedChanged({
Update-CertGenerationUI
})
$saveDefaultsBtn.Add_Click({
$defaults = [pscustomobject]@{
DefaultZone = $zoneBox.Text
TargetIp = $ipBox.Text
DnsServer = $dnsServerBox.Text
ReplicationTargets = $replicationTargetsBox.Text
ReplicationCommand = $replicationCmdBox.Text
ReplicationEnabled = $replicationEnabledBox.Checked
ReplicationDelaySeconds = $replicationDelayBox.Text
ReplicationRemote = $replicationRemoteBox.Checked
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
DisableCertGeneration = $disableCertsBox.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."
}
})
$clearBtn.Add_Click({
$logBox.Clear()
})
$runBtn.Add_Click({
$runBtn.Enabled = $false
try {
$hosts = @()
$hosts += Split-List $hostsBox.Text
if (-not [string]::IsNullOrWhiteSpace($fileBox.Text)) {
if (-not (Test-Path -Path $fileBox.Text -PathType Leaf)) {
throw "Hostnames file not found: $($fileBox.Text)"
}
$fileHosts = Get-Content -Path $fileBox.Text | ForEach-Object { $_.Trim() } | Where-Object { $_ }
$hosts += $fileHosts
}
if ($hosts.Count -eq 0) { throw "No hostnames provided." }
$zone = $zoneBox.Text.Trim()
if (-not $zone) { throw "Default DNS zone is required." }
$targetIp = $ipBox.Text.Trim()
if (-not $targetIp) { throw "Target IPv4 is required." }
$selectedReplicationTargets = @($dnsListBox.SelectedItems | ForEach-Object { $_.ToString() })
$dnsServer = $dnsServerBox.Text.Trim()
if (-not $dnsServer -and $selectedReplicationTargets.Count -gt 0) {
$dnsServer = $selectedReplicationTargets[0]
}
if (-not $dnsServer) { throw "Primary DNS server is required." }
$outputType = $outputTypeBox.SelectedItem.ToString()
$outputPath = $outputPathBox.Text.Trim()
if (-not $outputPath) { throw "Output path is required." }
if ($outputType -eq "PFX" -and [string]::IsNullOrWhiteSpace($pfxPasswordBox.Text)) {
throw "PFX password is required."
}
$hostEntries = @($hosts | ForEach-Object { Resolve-HostEntry -Name $_ -Zone $zone -UseProvidedFqdn $useFqdnBox.Checked } | Where-Object { $_ })
& $logAction "Processing $($hostEntries.Count) hostname(s)."
foreach ($entry in $hostEntries) {
Ensure-ARecord -Zone $zone -HostLabel $entry.HostLabel -TargetIp $targetIp -DnsServer $dnsServer -Log $logAction
}
if ($replicationEnabledBox.Checked) {
$replicationDelaySeconds = 0
$delayRaw = $replicationDelayBox.Text.Trim()
if (-not [string]::IsNullOrWhiteSpace($delayRaw)) {
if (-not [int]::TryParse($delayRaw, [ref]$replicationDelaySeconds) -or $replicationDelaySeconds -lt 0) {
throw "Replication wait seconds must be a non-negative integer."
}
}
if ($selectedReplicationTargets.Count -gt 0) {
$replicationTargets = $selectedReplicationTargets
} else {
$replicationTargets = @(Split-List $replicationTargetsBox.Text)
if ($replicationTargets.Count -eq 0 -and $dnsServer) {
$replicationTargets = @($dnsServer)
& $logAction "Replication targets empty; using primary DNS server $dnsServer."
}
}
Invoke-Replication -Servers $replicationTargets -Command $replicationCmdBox.Text -UseRemote $replicationRemoteBox.Checked -Log $logAction
if ($replicationDelaySeconds -gt 0) {
& $logAction "Waiting $replicationDelaySeconds seconds for replication."
Start-Sleep -Seconds $replicationDelaySeconds
}
} else {
& $logAction "Replication disabled."
}
if ($disableCertsBox.Checked) {
& $logAction "Cert generation disabled; DNS updates/replication only."
} elseif ($runWacsBox.Checked) {
$wacsPath = $wacsPathBox.Text.Trim()
if (-not (Test-Path -Path $wacsPath -PathType Leaf)) {
throw "WACS not found at: $wacsPath"
}
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 } | Where-Object { $_ })
& $logAction "Requesting one certificate with $($hostList.Count) hostname(s)."
Invoke-Wacs `
-WacsPath $wacsPath `
-HostFqdns $hostList `
-OutputType $outputType `
-OutputPath $outputPath `
-PfxPassword $pfxPasswordBox.Text `
-BaseUri $baseUriBox.Text.Trim() `
-Validation $validationBox.Text.Trim() `
-ValidationPort $validationPortBox.Text.Trim() `
-Verbose $verboseBox.Checked `
-Log $logAction
}
}
& $logAction "Done."
} catch {
& $logAction "Error: $($_.Exception.Message)"
} finally {
$runBtn.Enabled = $true
}
})
[void]$form.Add_Load({
try { Apply-Layout } catch {}
})
[void]$form.Add_Shown({
try { $form.BeginInvoke([Action]{ try { Apply-Layout } catch {} }) } catch {}
})
[void]$panel.Add_SizeChanged({
try { Apply-Layout } catch {}
})
[void]$form.Add_Resize({
try { Apply-Layout } catch {}
})
[void]$form.ShowDialog()