Add INF import and per-INF issuance
This commit is contained in:
162
certy.ps1
162
certy.ps1
@@ -234,6 +234,62 @@ function Get-DefaultValue {
|
|||||||
return $prop.Value
|
return $prop.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-HostsFromInfLines {
|
||||||
|
param([string[]]$Lines)
|
||||||
|
$set = New-Object System.Collections.Generic.HashSet[string] ([System.StringComparer]::OrdinalIgnoreCase)
|
||||||
|
if (-not $Lines) { return @() }
|
||||||
|
|
||||||
|
foreach ($line in $Lines) {
|
||||||
|
if ([string]::IsNullOrWhiteSpace($line)) { continue }
|
||||||
|
if ($line.TrimStart() -match '^[;#]') { continue }
|
||||||
|
$matches = [regex]::Matches($line, '(?i)\bdns\s*=\s*([^&",\s]+)')
|
||||||
|
foreach ($match in $matches) {
|
||||||
|
$value = $match.Groups[1].Value.Trim()
|
||||||
|
if ($value) { [void]$set.Add($value) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($set.Count -gt 0) { return $set | Sort-Object }
|
||||||
|
|
||||||
|
$subjectLine = $Lines | Where-Object { $_ -match '(?i)^\s*subject\s*=' } | Select-Object -First 1
|
||||||
|
if ($subjectLine -and ($subjectLine -match '(?i)\bCN\s*=\s*([^,"]+)')) {
|
||||||
|
$cn = $Matches[1].Trim()
|
||||||
|
if ($cn) { [void]$set.Add($cn) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return $set | Sort-Object
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-InfSubjectLines {
|
||||||
|
param([string[]]$Lines)
|
||||||
|
$removed = $false
|
||||||
|
$filtered = foreach ($line in $Lines) {
|
||||||
|
if ($line -match '(?i)^\s*subject\s*=') {
|
||||||
|
$removed = $true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$line
|
||||||
|
}
|
||||||
|
return [pscustomobject]@{
|
||||||
|
Lines = $filtered
|
||||||
|
Removed = $removed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Save-SanitizedInf {
|
||||||
|
param(
|
||||||
|
[string]$FileName,
|
||||||
|
[string[]]$Lines
|
||||||
|
)
|
||||||
|
$dir = Join-Path $env:ProgramData "Certy\\inf-sanitized"
|
||||||
|
if (-not (Test-Path -Path $dir -PathType Container)) {
|
||||||
|
New-Item -Path $dir -ItemType Directory -Force | Out-Null
|
||||||
|
}
|
||||||
|
$outPath = Join-Path $dir $FileName
|
||||||
|
Set-Content -Path $outPath -Value $Lines -Encoding ascii
|
||||||
|
return $outPath
|
||||||
|
}
|
||||||
|
|
||||||
function Resolve-HostEntry {
|
function Resolve-HostEntry {
|
||||||
param(
|
param(
|
||||||
[string]$Name,
|
[string]$Name,
|
||||||
@@ -712,6 +768,22 @@ $panel.Controls.Add($csrImportBtn)
|
|||||||
Style-ButtonSecondary $csrImportBtn
|
Style-ButtonSecondary $csrImportBtn
|
||||||
$y += $rowHeight + $gap
|
$y += $rowHeight + $gap
|
||||||
|
|
||||||
|
$infLabel = Add-Label "INF folder (optional)" $xLabel $y $labelWidth $rowHeight
|
||||||
|
$infFolderBox = Add-TextBox $xInput $y ($inputWidth - ((2 * $buttonWidth) + $buttonGap)) $rowHeight $false
|
||||||
|
$infBrowseBtn = New-Object System.Windows.Forms.Button
|
||||||
|
$infBrowseBtn.Text = "Browse"
|
||||||
|
$infBrowseBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - (2 * $buttonWidth + $buttonGap)), ($y - 1))
|
||||||
|
$infBrowseBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
|
||||||
|
$panel.Controls.Add($infBrowseBtn)
|
||||||
|
Style-ButtonSecondary $infBrowseBtn
|
||||||
|
$infImportBtn = New-Object System.Windows.Forms.Button
|
||||||
|
$infImportBtn.Text = "Import INF"
|
||||||
|
$infImportBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
|
||||||
|
$infImportBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
|
||||||
|
$panel.Controls.Add($infImportBtn)
|
||||||
|
Style-ButtonSecondary $infImportBtn
|
||||||
|
$y += $rowHeight + $gap
|
||||||
|
|
||||||
$useFqdnBox = Add-CheckBox "Input contains FQDNs (otherwise default zone is appended)" $xInput $y $inputWidth $rowHeight
|
$useFqdnBox = Add-CheckBox "Input contains FQDNs (otherwise default zone is appended)" $xInput $y $inputWidth $rowHeight
|
||||||
$y += $rowHeight + $gap
|
$y += $rowHeight + $gap
|
||||||
|
|
||||||
@@ -909,6 +981,8 @@ $sectionAnchors = @{
|
|||||||
Logs = $sectionActivity
|
Logs = $sectionActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$script:infRequests = @()
|
||||||
|
|
||||||
foreach ($key in $sectionAnchors.Keys) {
|
foreach ($key in $sectionAnchors.Keys) {
|
||||||
$label = $navLabels[$key]
|
$label = $navLabels[$key]
|
||||||
if (-not $label) { continue }
|
if (-not $label) { continue }
|
||||||
@@ -996,7 +1070,7 @@ function Show-HelpDialog {
|
|||||||
|
|
||||||
$stepY = 28
|
$stepY = 28
|
||||||
$steps = @(
|
$steps = @(
|
||||||
"Add hostnames (one per line, CSV, or CSR folder) then click 1) Preview.",
|
"Add hostnames (one per line, CSV, CSR, or INF folder) then click 1) Preview.",
|
||||||
"Click 2) Scan and select the DNS server (Primary is 10.106.60.1). Replication runs from there.",
|
"Click 2) Scan and select the DNS server (Primary is 10.106.60.1). Replication runs from there.",
|
||||||
"Set the replication wait time. Typical value: 15 minutes.",
|
"Set the replication wait time. Typical value: 15 minutes.",
|
||||||
"Choose output type (PEM or PFX). If PFX, supply a password.",
|
"Choose output type (PEM or PFX). If PFX, supply a password.",
|
||||||
@@ -1192,6 +1266,9 @@ function Apply-Layout {
|
|||||||
$csrFolderBox.Width = $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
|
$csrFolderBox.Width = $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
|
||||||
$csrBrowseBtn.Left = $xInput + $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
|
$csrBrowseBtn.Left = $xInput + $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
|
||||||
$csrImportBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
|
$csrImportBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
|
||||||
|
$infFolderBox.Width = $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
|
||||||
|
$infBrowseBtn.Left = $xInput + $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
|
||||||
|
$infImportBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
|
||||||
$zoneBox.Width = $inputWidthCalc
|
$zoneBox.Width = $inputWidthCalc
|
||||||
$ipBox.Width = $inputWidthCalc - ($buttonWidth + $buttonGap)
|
$ipBox.Width = $inputWidthCalc - ($buttonWidth + $buttonGap)
|
||||||
$ipRefreshBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
|
$ipRefreshBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
|
||||||
@@ -1276,6 +1353,13 @@ $csrBrowseBtn.Add_Click({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$infBrowseBtn.Add_Click({
|
||||||
|
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
|
||||||
|
if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
||||||
|
$infFolderBox.Text = $dialog.SelectedPath
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
$csrImportBtn.Add_Click({
|
$csrImportBtn.Add_Click({
|
||||||
try {
|
try {
|
||||||
$folder = $csrFolderBox.Text.Trim()
|
$folder = $csrFolderBox.Text.Trim()
|
||||||
@@ -1298,6 +1382,58 @@ $csrImportBtn.Add_Click({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$infImportBtn.Add_Click({
|
||||||
|
try {
|
||||||
|
$folder = $infFolderBox.Text.Trim()
|
||||||
|
if (-not $folder) { throw "INF folder is empty." }
|
||||||
|
if (-not (Test-Path -Path $folder -PathType Container)) { throw "INF folder not found: $folder" }
|
||||||
|
|
||||||
|
$infFiles = Get-ChildItem -Path $folder -Filter *.inf -File -Recurse
|
||||||
|
if (-not $infFiles) {
|
||||||
|
& $logAction "No INF files found in $folder"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$script:infRequests = @()
|
||||||
|
$infHosts = @()
|
||||||
|
$subjectRemovedCount = 0
|
||||||
|
|
||||||
|
foreach ($infFile in $infFiles) {
|
||||||
|
$lines = Get-Content -Path $infFile.FullName
|
||||||
|
$hosts = @(Get-HostsFromInfLines -Lines $lines)
|
||||||
|
$sanitize = Remove-InfSubjectLines -Lines $lines
|
||||||
|
$sanitizedPath = Save-SanitizedInf -FileName $infFile.Name -Lines $sanitize.Lines
|
||||||
|
if ($sanitize.Removed) { $subjectRemovedCount++ }
|
||||||
|
|
||||||
|
if ($hosts.Count -eq 0) {
|
||||||
|
& $logAction "INF $($infFile.Name): no hostnames detected."
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$script:infRequests += [pscustomobject]@{
|
||||||
|
File = $infFile.FullName
|
||||||
|
Hosts = $hosts
|
||||||
|
Sanitized = $sanitizedPath
|
||||||
|
}
|
||||||
|
$infHosts += $hosts
|
||||||
|
}
|
||||||
|
|
||||||
|
$infHosts = @($infHosts | Where-Object { $_ } | Sort-Object -Unique)
|
||||||
|
if ($infHosts.Count -gt 0) {
|
||||||
|
$currentHosts = Split-List $hostsBox.Text
|
||||||
|
$merged = Merge-Hostnames -Existing $currentHosts -NewItems $infHosts
|
||||||
|
$hostsBox.Text = ($merged -join [Environment]::NewLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
& $logAction "Imported $($infFiles.Count) INF file(s), added $($infHosts.Count) hostname(s)."
|
||||||
|
if ($subjectRemovedCount -gt 0) {
|
||||||
|
& $logAction "Removed Subject line from $subjectRemovedCount INF file(s) (sanitized copies saved)."
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
& $logAction "Error: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
$dnsScanBtn.Add_Click({
|
$dnsScanBtn.Add_Click({
|
||||||
try {
|
try {
|
||||||
$dnsListBox.Items.Clear()
|
$dnsListBox.Items.Clear()
|
||||||
@@ -1506,7 +1642,29 @@ $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"
|
||||||
}
|
}
|
||||||
if ($perHostBox.Checked) {
|
if ($script:infRequests -and $script:infRequests.Count -gt 0) {
|
||||||
|
& $logAction "INF requests detected; issuing one certificate per INF file."
|
||||||
|
foreach ($req in $script:infRequests) {
|
||||||
|
$reqEntries = @($req.Hosts | ForEach-Object { Resolve-HostEntry -Name $_ -Zone $zone -UseProvidedFqdn $useFqdnBox.Checked } | Where-Object { $_ })
|
||||||
|
$reqFqdns = @($reqEntries | ForEach-Object { $_.Fqdn } | Where-Object { $_ })
|
||||||
|
if ($reqFqdns.Count -eq 0) {
|
||||||
|
& $logAction "INF $([System.IO.Path]::GetFileName($req.File)) skipped (no hosts)."
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
& $logAction "Requesting certificate for INF $([System.IO.Path]::GetFileName($req.File)) with $($reqFqdns.Count) hostname(s)."
|
||||||
|
Invoke-Wacs `
|
||||||
|
-WacsPath $wacsPath `
|
||||||
|
-HostFqdns $reqFqdns `
|
||||||
|
-OutputType $outputType `
|
||||||
|
-OutputPath $outputPath `
|
||||||
|
-PfxPassword ($(if ($usePfxPassword) { $pfxPasswordBox.Text } else { "" })) `
|
||||||
|
-BaseUri $baseUriBox.Text.Trim() `
|
||||||
|
-Validation $validationBox.Text.Trim() `
|
||||||
|
-ValidationPort $validationPortBox.Text.Trim() `
|
||||||
|
-Verbose $verboseBox.Checked `
|
||||||
|
-Log $logAction
|
||||||
|
}
|
||||||
|
} elseif ($perHostBox.Checked) {
|
||||||
foreach ($entry in $hostEntries) {
|
foreach ($entry in $hostEntries) {
|
||||||
& $logAction "Requesting certificate for $($entry.Fqdn)."
|
& $logAction "Requesting certificate for $($entry.Fqdn)."
|
||||||
Invoke-Wacs `
|
Invoke-Wacs `
|
||||||
|
|||||||
Reference in New Issue
Block a user