1006 lines
38 KiB
PowerShell
1006 lines
38 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 (-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 ($host in $fqdnHosts) {
|
|
$clean = $host.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 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,
|
|
[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) {
|
|
$cmd = if ($Command -match "\{server\}") { $Command.Replace("{server}", $server) } else { $Command }
|
|
$cmd = $cmd.Trim()
|
|
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} /A /e /P /d"
|
|
$y += $rowHeight + ($gap * 2)
|
|
|
|
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 * 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
|
|
}
|
|
}
|
|
|
|
$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 ($null -ne $loadedDefaults.ReplicationEnabled) { $replicationEnabledBox.Checked = [bool]$loadedDefaults.ReplicationEnabled }
|
|
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
|
|
Update-ReplicationUI
|
|
& $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
|
|
$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
|
|
}
|
|
|
|
function Update-ReplicationUI {
|
|
$enabled = $replicationEnabledBox.Checked
|
|
$replicationTargetsBox.Enabled = $enabled
|
|
$replicationFromSelectedBtn.Enabled = $enabled
|
|
$primaryFromSelectedBtn.Enabled = $enabled
|
|
$replicationCmdBox.Enabled = $enabled
|
|
$dnsListBox.Enabled = $enabled
|
|
}
|
|
|
|
$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({
|
|
Update-ReplicationUI
|
|
})
|
|
|
|
$filePreviewBtn.Add_Click({
|
|
$filePreviewBox.Text = Get-FilePreview -Path $fileBox.Text.Trim()
|
|
if (Test-Path -Path $fileBox.Text.Trim() -PathType Leaf) {
|
|
$fileHosts = Get-Content -Path $fileBox.Text.Trim() | 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
|
|
})
|
|
|
|
$saveDefaultsBtn.Add_Click({
|
|
$defaults = [pscustomobject]@{
|
|
DefaultZone = $zoneBox.Text
|
|
TargetIp = $ipBox.Text
|
|
DnsServer = $dnsServerBox.Text
|
|
ReplicationTargets = $replicationTargetsBox.Text
|
|
ReplicationCommand = $replicationCmdBox.Text
|
|
ReplicationEnabled = $replicationEnabledBox.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
|
|
}
|
|
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) {
|
|
if ($selectedReplicationTargets.Count -gt 0) {
|
|
$replicationTargets = $selectedReplicationTargets
|
|
} else {
|
|
$replicationTargets = Split-List $replicationTargetsBox.Text
|
|
}
|
|
Invoke-Replication -Servers $replicationTargets -Command $replicationCmdBox.Text -Log $logAction
|
|
} else {
|
|
& $logAction "Replication disabled."
|
|
}
|
|
|
|
if ($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 }
|
|
& $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({ Apply-Layout })
|
|
[void]$form.Add_Shown({ $form.BeginInvoke([Action]{ Apply-Layout }) })
|
|
[void]$panel.Add_SizeChanged({ Apply-Layout })
|
|
[void]$form.Add_Resize({ Apply-Layout })
|
|
|
|
[void]$form.ShowDialog()
|