diff --git a/certy.ps1 b/certy.ps1 index daa6c80..b0b7f9f 100644 --- a/certy.ps1 +++ b/certy.ps1 @@ -279,9 +279,10 @@ function Remove-InfSubjectLines { function Save-SanitizedInf { param( [string]$FileName, - [string[]]$Lines + [string[]]$Lines, + [string]$Subdir = "inf-sanitized" ) - $dir = Join-Path $env:ProgramData "Certy\\inf-sanitized" + $dir = Join-Path $env:ProgramData ("Certy\\" + $Subdir) if (-not (Test-Path -Path $dir -PathType Container)) { New-Item -Path $dir -ItemType Directory -Force | Out-Null } @@ -290,6 +291,36 @@ function Save-SanitizedInf { return $outPath } +function Sanitize-InfSubjectForCsr { + param([string[]]$Lines) + $updated = $false + $output = foreach ($line in $Lines) { + if ($line -match '(?i)^\s*subject\s*=\s*(.+)$') { + $value = $Matches[1].Trim() + $value = $value.Trim('"') + $parts = $value -split '\s*,\s*' | Where-Object { $_ } + $kept = @() + foreach ($part in $parts) { + if ($part -match '^(?i)OU\s*=') { continue } + $kept += $part.Trim() + } + if ($kept.Count -gt 0) { + $updated = $true + "Subject = `"$($kept -join ', ')`"" + } else { + $updated = $true + continue + } + } else { + $line + } + } + return [pscustomobject]@{ + Lines = $output + Updated = $updated + } +} + function Resolve-HostEntry { param( [string]$Name, @@ -784,6 +815,17 @@ $panel.Controls.Add($infImportBtn) Style-ButtonSecondary $infImportBtn $y += $rowHeight + $gap +$infCsrOnlyBox = Add-CheckBox "Generate CSR from INF only (skip WACS)" $xInput $y $inputWidth $rowHeight +$infCsrOnlyBox.Checked = $false +$y += $rowHeight + $gap + +$infCsrOutputLabel = Add-Label "CSR output folder" $xLabel $y $labelWidth $rowHeight +$infCsrOutputBox = Add-TextBox $xInput $y $inputWidth $rowHeight $false +$infCsrOutputBox.Text = "C:\ProgramData\Certy\csr-output" +$infCsrOutputLabel.Enabled = $false +$infCsrOutputBox.Enabled = $false +$y += $rowHeight + $gap + $useFqdnBox = Add-CheckBox "Input contains FQDNs (otherwise default zone is appended)" $xInput $y $inputWidth $rowHeight $y += $rowHeight + $gap @@ -1109,7 +1151,8 @@ function Show-HelpDialog { $optY = $stepY + 36 $options = @( "Turn off cert generation (DNS-only mode) to add DNS now and generate certs later.", - "Disable DNS replication if records already point correctly and you only need a renewal." + "Disable DNS replication if records already point correctly and you only need a renewal.", + "Generate CSR from INF only to produce .req files without running WACS." ) foreach ($opt in $options) { $bullet = New-Object System.Windows.Forms.Label @@ -1154,7 +1197,7 @@ function Update-ReplicationUI { } function Update-CertGenerationUI { - $disabled = $disableCertsBox.Checked + $disabled = $disableCertsBox.Checked -or $infCsrOnlyBox.Checked $perHostBox.Enabled = -not $disabled $wacsPathBox.Enabled = -not $disabled @@ -1167,6 +1210,14 @@ function Update-CertGenerationUI { $validationPortBox.Enabled = -not $disabled } +function Update-InfCsrUI { + $enabled = $infCsrOnlyBox.Checked + $infCsrOutputLabel.Enabled = $enabled + $infCsrOutputBox.Enabled = $enabled +} + +Update-InfCsrUI + function Update-ZoneFromHostInput { $hostList = @(Split-List $hostsBox.Text) if ($hostList.Count -eq 0) { return } @@ -1240,9 +1291,19 @@ if ($loadedDefaults) { if (-not [string]::IsNullOrWhiteSpace($value)) { $outputTypeBox.SelectedItem = $value } if (-not $outputTypeBox.SelectedItem) { $outputTypeBox.SelectedIndex = 0 } + $value = Get-DefaultValue -Defaults $loadedDefaults -Name "InfFolder" + if (-not [string]::IsNullOrWhiteSpace($value)) { $infFolderBox.Text = $value } + + $value = Get-DefaultValue -Defaults $loadedDefaults -Name "InfCsrOnly" + if ($null -ne $value) { $infCsrOnlyBox.Checked = [bool]$value } + + $value = Get-DefaultValue -Defaults $loadedDefaults -Name "InfCsrOutput" + if (-not [string]::IsNullOrWhiteSpace($value)) { $infCsrOutputBox.Text = $value } + Update-OutputTypeUI if (Test-Path function:Update-ReplicationUI) { Update-ReplicationUI } Update-CertGenerationUI + Update-InfCsrUI & $logAction "Defaults loaded from $(Get-DefaultsPath)." } @@ -1269,6 +1330,8 @@ function Apply-Layout { $infFolderBox.Width = $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap) $infBrowseBtn.Left = $xInput + $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap) $infImportBtn.Left = $xInput + $inputWidthCalc - $buttonWidth + $infCsrOutputBox.Width = $inputWidthCalc + $infCsrOnlyBox.Width = $inputWidthCalc $zoneBox.Width = $inputWidthCalc $ipBox.Width = $inputWidthCalc - ($buttonWidth + $buttonGap) $ipRefreshBtn.Left = $xInput + $inputWidthCalc - $buttonWidth @@ -1402,8 +1465,10 @@ $infImportBtn.Add_Click({ $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 + $sanitizedPath = Save-SanitizedInf -FileName $infFile.Name -Lines $sanitize.Lines -Subdir "inf-sanitized" if ($sanitize.Removed) { $subjectRemovedCount++ } + $csrSanitize = Sanitize-InfSubjectForCsr -Lines $lines + $csrInfPath = Save-SanitizedInf -FileName $infFile.Name -Lines $csrSanitize.Lines -Subdir "inf-csr" if ($hosts.Count -eq 0) { & $logAction "INF $($infFile.Name): no hostnames detected." @@ -1414,6 +1479,7 @@ $infImportBtn.Add_Click({ File = $infFile.FullName Hosts = $hosts Sanitized = $sanitizedPath + CsrInf = $csrInfPath } $infHosts += $hosts } @@ -1512,6 +1578,11 @@ $disableCertsBox.Add_CheckedChanged({ Update-CertGenerationUI }) +$infCsrOnlyBox.Add_CheckedChanged({ + Update-CertGenerationUI + Update-InfCsrUI +}) + $saveDefaultsBtn.Add_Click({ $defaults = [pscustomobject]@{ DefaultZone = $zoneBox.Text @@ -1534,6 +1605,9 @@ $saveDefaultsBtn.Add_Click({ Verbose = $verboseBox.Checked PerHostCerts = $perHostBox.Checked DisableCertGeneration = $disableCertsBox.Checked + InfFolder = $infFolderBox.Text + InfCsrOnly = $infCsrOnlyBox.Checked + InfCsrOutput = $infCsrOutputBox.Text } Save-Defaults -Defaults $defaults & $logAction "Defaults saved to $(Get-DefaultsPath)." @@ -1635,7 +1709,29 @@ $runBtn.Add_Click({ & $logAction "Replication disabled." } - if ($disableCertsBox.Checked) { + if ($infCsrOnlyBox.Checked) { + if (-not $script:infRequests -or $script:infRequests.Count -eq 0) { + throw "INF CSR generation enabled, but no INF files were imported." + } + $csrOutputDir = $infCsrOutputBox.Text.Trim() + if (-not $csrOutputDir) { throw "CSR output folder is required." } + if (-not (Test-Path -Path $csrOutputDir -PathType Container)) { + New-Item -Path $csrOutputDir -ItemType Directory -Force | Out-Null + } + foreach ($req in $script:infRequests) { + $baseName = [System.IO.Path]::GetFileNameWithoutExtension($req.File) + $csrPath = Join-Path $csrOutputDir ($baseName + ".req") + if (Test-Path -Path $csrPath) { + $csrPath = Join-Path $csrOutputDir ($baseName + "-" + (Get-Date -Format "yyyyMMddHHmmss") + ".req") + } + $infPath = if ($req.CsrInf) { $req.CsrInf } else { $req.File } + & $logAction "Generating CSR from $([System.IO.Path]::GetFileName($infPath)) -> $csrPath" + $output = & certreq.exe -new $infPath $csrPath 2>&1 + foreach ($line in $output) { + & $logAction $line + } + } + } elseif ($disableCertsBox.Checked) { & $logAction "Cert generation disabled; DNS updates/replication only." } else { $wacsPath = $wacsPathBox.Text.Trim()