Src/Public/New-AsBuiltConfig.ps1

function New-AsBuiltConfig {
    <#
    .SYNOPSIS
        Creates As Built Report configuration files.
    .DESCRIPTION
        New-AsBuiltConfig starts a menu-driven procedure in the powershell console and asks the user a series of questions
        Answers to these questions are optionally saved in a JSON configuration file which can then be referenced using the
        -AsBuiltConfigFilePath parameter using New-AsBuiltReport, to save having to answer these questions again and also to allow
        the automation of New-AsBuiltReport.
 
        New-AsBuiltConfig will automatically be called by New-AsBuiltReport if the -AsBuiltConfigFilePath parameter is not specified
        If a user wants to generate a new As Built Report configuration without running a new report, this cmdlet is exported
        in the AsBuiltReport powershell module and can be called as a standalone cmdlet.
    .EXAMPLE
        New-AsBuiltConfig
 
        Starts the interactive configuration wizard to create a new As Built Report configuration file.
        You will be prompted for report author, company information, email settings, and save location.
    .EXAMPLE
        $Config = New-AsBuiltConfig
 
        Creates a new As Built Report configuration and stores it in the $Config variable.
        The configuration can be used with New-AsBuiltReport without specifying -AsBuiltConfigFilePath.
    .LINK
        https://github.com/AsBuiltReport/AsBuiltReport.Core
    .LINK
        https://www.asbuiltreport.com/user-guide/new-asbuiltconfig/
    #>


    [CmdletBinding()]
    param()

    begin {
        #Run section to prompt user for information about the As Built Report to be exported to JSON format (if saved)
        $global:Config = @{ }
        Initialize-LocalizedData -ModuleType 'Core' -ModuleBasePath (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -LanguageFile 'New-AsBuiltConfig'

    }

    process {
        Clear-Host
        #region Report configuration
        # Show As Built Report configuration banner
        Draw-AsciiBanner -Lines @($translate.ReportInfo.BannerTitle) -TextColor 'Cyan' -SeparatorColor 'Cyan'

        $ReportAuthor = Read-Host -Prompt ($translate.ReportInfo.ReportAuthor -f [System.Environment]::Username)
        if (($null -eq $ReportAuthor) -or ($ReportAuthor -eq "")) {
            $ReportAuthor = $([System.Environment]::Username)
        }

        $Config.Report = @{
            'Author' = $ReportAuthor
        }
        #endregion Report configuration

        #region Company configuration
        Clear-Host
        # Show Company configuration banner
        Draw-AsciiBanner -Lines @($translate.CompanyConfig.BannerTitle) -TextColor 'Cyan' -SeparatorColor 'Cyan'

        $CompanyInfo = Read-Host -Prompt $translate.CompanyConfig.CompanyInfo
        if (($null -eq $CompanyInfo) -or ($CompanyInfo -eq "")) {
            $CompanyInfo = "n"
        }
        while ("y", "n" -notcontains $CompanyInfo) {
            $CompanyInfo = Read-Host -Prompt $translate.CompanyConfig.CompanyInfo
            if (($null -eq $CompanyInfo) -or ($CompanyInfo -eq "")) {
                $CompanyInfo = "n"
            }
        }

        if ($CompanyInfo -eq 'y') {
            $CompanyFullName = Read-Host -Prompt $translate.CompanyConfig.CompanyFullName
            $CompanyShortName = Read-Host -Prompt $translate.CompanyConfig.CompanyShortName
            $CompanyContact = Read-Host -Prompt $translate.CompanyConfig.CompanyContact
            do {
                $CompanyEmail = Read-Host -Prompt $translate.CompanyConfig.CompanyEmail

                if (($null -eq $CompanyEmail) -or ($CompanyEmail -eq "")) {
                    # Allow blank/empty - no validation needed
                    $isValid = $true
                } else {
                    # Validate email format if text is entered
                    try {
                        $null = [System.Net.Mail.MailAddress]$CompanyEmail
                        $isValid = $true
                    } catch {
                        Write-Host ($translate.EmailConfig.InvalidEmail -f $CompanyEmail) -ForegroundColor Red
                        $isValid = $false
                    }
                }
            } while (-not $isValid)
            $CompanyPhone = Read-Host -Prompt $translate.CompanyConfig.CompanyPhone
            $CompanyAddress = Read-Host -Prompt $translate.CompanyConfig.CompanyAddress
        }

        $Config.Company = @{
            'FullName' = $CompanyFullName
            'ShortName' = $CompanyShortName
            'Contact' = $CompanyContact
            'Email' = $CompanyEmail
            'Phone' = $CompanyPhone
            'Address' = $CompanyAddress
        }
        #endregion Company configuration

        #region Email configuration
        Clear-Host
        # Show Email configuration banner
        Draw-AsciiBanner -Lines @($translate.EmailConfig.BannerTitle) -TextColor 'Cyan' -SeparatorColor 'Cyan'
        if (-not ($SendEmail)) {
            $ConfigureMailSettings = Read-Host -Prompt $translate.EmailConfig.ConfigureMailSettings
            if (($null -eq $ConfigureMailSettings) -or ($ConfigureMailSettings -eq "")) {
                $ConfigureMailSettings = "n"
            }
            while ("y", "n" -notcontains $ConfigureMailSettings) {
                $ConfigureMailSettings = Read-Host -Prompt $translate.EmailConfig.ConfigureMailSettings
                if (($null -eq $ConfigureMailSettings) -or ($ConfigureMailSettings -eq "")) {
                    $ConfigureMailSettings = "n"
                }
            }
        }
        if (($SendEmail) -or ($ConfigureMailSettings -eq "y")) {
            do {
                $MailServer = Read-Host -Prompt $translate.EmailConfig.MailServer

                if (($null -eq $MailServer) -or ($MailServer -eq "")) {
                    Write-Host $translate.EmailConfig.EmptyMailServerAddress -ForegroundColor Red
                    $isValid = $false
                } else {
                    # Check if it's a valid IPv4 address (strict validation)
                    $ipParts = $MailServer.Split('.')
                    $isValidIPv4 = $false

                    if ($ipParts.Count -eq 4) {
                        $isValidIPv4 = $true
                        foreach ($part in $ipParts) {
                            $num = 0
                            if (-not [int]::TryParse($part, [ref]$num) -or $num -lt 0 -or $num -gt 255) {
                                $isValidIPv4 = $false
                                break
                            }
                        }
                    }

                    # Check if it's a valid IPv6 address
                    $isValidIPv6 = $false
                    if (-not $isValidIPv4) {
                        try {
                            $parsedIP = [System.Net.IPAddress]::Parse($MailServer)
                            $isValidIPv6 = $parsedIP.AddressFamily -eq 'InterNetworkV6'
                        } catch {
                            $isValidIPv6 = $false
                        }
                    }

                    # Check if it's a valid FQDN - but exclude things that look like incomplete IPs
                    $isValidFQDN = $false
                    if (-not $isValidIPv4 -and -not $isValidIPv6) {
                        # Exclude strings that are all numeric with dots (incomplete IPs)
                        $looksLikeIP = $MailServer -match '^[\d\.]+$'

                        if (-not $looksLikeIP) {
                            $fqdnPattern = '^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)+$'
                            $isValidFQDN = $MailServer -match $fqdnPattern
                        }
                    }

                    if ($isValidIPv4 -or $isValidIPv6 -or $isValidFQDN) {
                        $isValid = $true
                    } else {
                        Write-Host ($translate.EmailConfig.InvalidMailServer -f $MailServer) -ForegroundColor Red
                        $isValid = $false
                    }
                }
            } while (-not $isValid)

            if (($MailServer -eq 'smtp.office365.com') -or ($MailServer -eq 'smtp.gmail.com')) {
                do {
                    $MailServerPort = Read-Host -Prompt $translate.EmailConfig.MailServerPort587
                    $portNumber = 0  # Initialize the variable

                    if (($null -eq $MailServerPort) -or ($MailServerPort -eq "")) {
                        $MailServerPort = '587'
                        $isValid = $true
                    } elseif ([int]::TryParse($MailServerPort, [ref]$portNumber) -and $portNumber -ge 1 -and $portNumber -le 65535) {
                        $isValid = $true
                    } else {
                        Write-Host $translate.EmailConfig.InvalidMailServerPort587 -ForegroundColor Red
                        $isValid = $false
                    }
                } while (-not $isValid)
            } else {
                do {
                    $MailServerPort = Read-Host -Prompt $translate.EmailConfig.MailServerPort25
                    $portNumber = 0  # Initialize the variable

                    if (($null -eq $MailServerPort) -or ($MailServerPort -eq "")) {
                        $MailServerPort = '25'
                        $isValid = $true
                    } elseif ([int]::TryParse($MailServerPort, [ref]$portNumber) -and $portNumber -ge 1 -and $portNumber -le 65535) {
                        $isValid = $true
                    } else {
                        Write-Host $translate.EmailConfig.InvalidMailServerPort25 -ForegroundColor Red
                        $isValid = $false
                    }
                } while (-not $isValid)
            }

            $MailServerUseSSL = Read-Host -Prompt $translate.EmailConfig.MailServerUseSSL
            if (($null -eq $MailServerUseSSL) -or ($MailServerUseSSL -eq "")) {
                $MailServerUseSSL = "n"
            }
            while ("y", "n" -notcontains $MailServerUseSSL) {
                $MailServerUseSSL = Read-Host -Prompt $translate.EmailConfig.MailServerUseSSL
                if (($null -eq $MailServerUseSSL) -or ($MailServerUseSSL -eq "")) {
                    $MailServerUseSSL = "n"
                }
            }
            $MailServerUseSSL = switch ($MailServerUseSSL) {
                "y" { $true }
                "n" { $false }
            }

            $MailCredentials = Read-Host -Prompt $translate.EmailConfig.MailCredentials
            if (($null -eq $MailCredentials) -or ($MailCredentials -eq "")) {
                $MailCredentials = "n"
            }
            while ("y", "n" -notcontains $MailCredentials) {
                $MailCredentials = Read-Host -Prompt $translate.EmailConfig.MailCredentials
                if (($null -eq $MailCredentials) -or ($MailCredentials -eq "")) {
                    $MailCredentials = "n"
                }
            }
            $MailCredentials = switch ($MailCredentials) {
                "y" { $true }
                "n" { $false }
            }

            do {
                $MailFrom = Read-Host -Prompt $translate.EmailConfig.MailFrom
                if (($null -eq $MailFrom) -or ($MailFrom -eq "")) {
                    Write-Host $translate.EmailConfig.EmptyEmail -ForegroundColor Red
                    $isValid = $false
                } else {
                    try {
                        $null = [System.Net.Mail.MailAddress]$MailFrom
                        $isValid = $true
                    } catch {
                        $isValid = $false
                        Write-Host ($translate.EmailConfig.InvalidEmail -f $MailFrom) -ForegroundColor Red
                    }
                }
            } while (-not $isValid)

            $MailRecipients = @()
            do {
                do {
                    $MailTo = Read-Host -Prompt $translate.EmailConfig.MailTo

                    if (($null -eq $MailTo) -or ($MailTo -eq "")) {
                        Write-Host $translate.EmailConfig.EmptyEmail -ForegroundColor Red
                        $isValid = $false
                    } else {
                        try {
                            $null = [System.Net.Mail.MailAddress]$MailTo
                            $isValidEmail = $true
                        } catch {
                            $isValidEmail = $false
                            Write-Host ($translate.EmailConfig.InvalidEmail -f $MailTo) -ForegroundColor Red
                        }
                    }
                } while (-not $isValidEmail)

                $MailRecipients += $MailTo
                $AnotherRecipient = @()
                $AnotherRecipient = Read-Host -Prompt $translate.EmailConfig.AnotherRecipient
                if (($null -eq $AnotherRecipient) -or ($AnotherRecipient -eq "")) {
                    $AnotherRecipient = "n"
                }
                while ("y", "n" -notcontains $AnotherRecipient) {
                    $AnotherRecipient = Read-Host -Prompt $translate.EmailConfig.AnotherRecipient
                    if (($null -eq $AnotherRecipient) -or ($AnotherRecipient -eq "")) {
                        $AnotherRecipient = "n"
                    }
                }
            } until($AnotherRecipient -eq "n")
            $MailBody = Read-Host -Prompt $translate.EmailConfig.MailBodyPrompt
            if (($null -eq $MailBody) -or ($MailBody -eq "")) {
                $MailBody = $translate.EmailConfig.MailBody
            }
        }

        $Config.Email = @{
            'Server' = $MailServer
            'Port' = $MailServerPort
            'UseSSL' = $MailServerUseSSL
            'Credentials' = $MailCredentials
            'From' = $MailFrom
            'To' = $MailRecipients
            'Body' = $MailBody
        }
        #endregion Email Configuration

        #region Report Configuration Folder
        if ($Report -and (-not $ReportConfigFilePath)) {
            Clear-Host
            # Show Rerport configuration banner
            Draw-AsciiBanner -Lines @($translate.ReportConfig.BannerTitle) -TextColor 'Cyan' -SeparatorColor 'Cyan'
            $DefaultConfigFolder = Join-Path -Path $Home -ChildPath "AsBuiltReport"
            $ReportConfigFolder = Read-Host -Prompt ($translate.ReportConfig.ReportConfigFolder -f $DefaultConfigFolder)
            if (($null -eq $ReportConfigFolder) -or ($ReportConfigFolder -eq "")) {
                $ReportConfigFolder = $DefaultConfigFolder
            }

            #If the folder doesn't exist, create it
            if (-not (Test-Path -Path $ReportConfigFolder)) {
                try {
                    $Folder = New-Item -Path $ReportConfigFolder -ItemType Directory -Force
                } catch {
                    Write-Error $_
                    break
                }
            }

            # Add the path to the folder to the report configuration file
            $Config.UserFolder = @{
                'Path' = $ReportConfigFolder
            }

            # Test to see if the report configuration file exists. If it doesn't exist, generate the report configuration file.
            # If the report configuration file exists, prompt the user to overwrite the report configuration file.
            $ReportModule = Get-Module -Name "AsBuiltReport.$Report" -ListAvailable | Sort-Object -Property Version -Descending | Select-Object -First 1
            $ReportModuleName = $ReportModule.Name
            $SourcePath = Join-Path -Path $($ReportModule.ModuleBase) -ChildPath "$ReportModuleName.json"
            $DestinationPath = Join-Path -Path $($ReportConfigFolder) -ChildPath "$ReportModuleName.json"
            if (-not (Get-ChildItem -Path $DestinationPath)) {
                Write-Verbose ($translate.CopyFile -f $SourcePath, $DestinationPath)
                New-AsBuiltReportConfig -Report $Report -FolderPath $ReportConfigFolder
                # Restore core translations after New-AsBuiltReportConfig (which loads its own translations)
                Initialize-LocalizedData -ModuleType 'Core' -ModuleBasePath (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -LanguageFile 'New-AsBuiltConfig'
            } else {
                try {
                    if (Test-Path -Path $DestinationPath) {
                        $OverwriteReportConfig = Read-Host -Prompt ($translate.ReportConfig.OverwriteReportConfig -f $ReportModuleName)
                        if (($null -eq $OverwriteReportConfig) -or ($OverwriteReportConfig -eq "")) {
                            $OverwriteReportConfig = "n"
                        }
                        while ("y", "n" -notcontains $OverwriteReportConfig) {
                            $OverwriteReportConfig = Read-Host -Prompt ($translate.ReportConfig.OverwriteReportConfig -f $ReportModuleName)
                            if (($null -eq $OverwriteReportConfig) -or ($OverwriteReportConfig -eq "")) {
                                $OverwriteReportConfig = "n"
                            }
                        }
                        if ($OverwriteReportConfig -eq 'y') {
                            try {
                                Write-Verbose ($translate.ReportConfig.OverwriteFile -f $SourcePath, $DestinationPath)
                                New-AsBuiltReportConfig -Report $Report -FolderPath $ReportConfigFolder -Force
                                # Restore core translations after New-AsBuiltReportConfig (which loads its own translations)
                                Initialize-LocalizedData -ModuleType 'Core' -ModuleBasePath (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -LanguageFile 'New-AsBuiltConfig'
                            } catch {
                                Write-Error $_
                                break
                            }
                        }
                    }
                } catch {
                    Write-Error $_
                }
            }
        }
        #endregion Report Configuration Folder

        #region Save configuration
        Clear-Host
        Draw-AsciiBanner -Lines @($translate.ReportConfig.BannerTitle) -TextColor 'Cyan' -SeparatorColor 'Cyan'
        $SaveAsBuiltConfig = Read-Host -Prompt $translate.ReportConfig.SaveAsBuiltConfig
        if (($null -eq $SaveAsBuiltConfig) -or ($SaveAsBuiltConfig -eq "")) {
            $SaveAsBuiltConfig = "y"
        }
        while ("y", "n" -notcontains $SaveAsBuiltConfig) {
            $SaveAsBuiltConfig = Read-Host -Prompt $translate.ReportConfig.SaveAsBuiltConfig
            if (($null -eq $SaveAsBuiltConfig) -or ($SaveAsBuiltConfig -eq "")) {
                $SaveAsBuiltConfig = "y"
            }
        }

        if ($SaveAsBuiltConfig -eq 'y') {
            $AsBuiltName = Read-Host -Prompt $translate.ReportConfig.AsBuiltName
            if (($null -eq $AsBuiltName) -or ($AsBuiltName -eq "")) {
                $AsBuiltName = "AsBuiltReport"
            }
            if ($Config.UserFolder.Path) {
                $AsBuiltExportPath = Read-Host -Prompt ($translate.ReportConfig.AsBuiltExportPath -f $Config.UserFolder.Path)
                if (($null -eq $AsBuiltExportPath) -or ($AsBuiltExportPath -eq "")) {
                    $AsBuiltExportPath = $Config.UserFolder.Path
                }
            } elseif ($ReportConfigFilePath) {
                $ReportConfigFolderPath = Split-Path -Path $ReportConfigFilePath
                $AsBuiltExportPath = Read-Host -Prompt ($translate.ReportConfig.AsBuiltExportPath -f $ReportConfigFolderPath)
                if (($null -eq $AsBuiltExportPath) -or ($AsBuiltExportPath -eq "")) {
                    $AsBuiltExportPath = $ReportConfigFolderPath
                }
            } else {
                $DefaultConfigFolder = Join-Path -Path $Home -ChildPath "AsBuiltReport"
                $AsBuiltExportPath = Read-Host -Prompt ($translate.ReportConfig.AsBuiltExportPath -f $DefaultConfigFolder)
                if (($null -eq $AsBuiltExportPath) -or ($AsBuiltExportPath -eq "")) {
                    $AsBuiltExportPath = $DefaultConfigFolder
                }
            }
            if (-not (Test-Path -Path $AsBuiltExportPath)) {
                Write-Verbose ($translate.ReportConfig.ConfigFolder -f $AsBuiltExportPath)
                try {
                    $Folder = New-Item -Path $AsBuiltExportPath -ItemType Directory -Force
                } catch {
                    Write-Error $_
                    break
                }
            }
            $Config.UserFolder = @{
                'Path' = $AsBuiltExportPath
            }
            Write-Verbose ($translate.ReportConfig.SaveConfig -f $AsBuiltName, $AsBuiltExportPath)
            $AsBuiltConfigPath = Join-Path -Path $AsBuiltExportPath -ChildPath "$AsBuiltName.json"
            $Config | ConvertTo-Json | Out-File $AsBuiltConfigPath
        } else {
            Write-Verbose $translate.ReportConfig.NotSaved
        }
        #endregion Save configuration

        # Print output to screen so that it can be captured to $Global:AsBuiltConfig variable in New-AsBuiltReport
        $Config

        # Verbose Output
        Write-Verbose "Config.Report.Author = $ReportAuthor"
        Write-Verbose "Config.UserFolder.Path = $ReportConfigFolder"
        foreach ($x in $Config.Company.Keys) {
            Write-Verbose "Config.Company.$x = $($Config.Company[$x])"
        }
        foreach ($x in $Config.Email.Keys) {
            Write-Verbose "Config.Email.$x = $($Config.Email[$x])"
        }
        Clear-Host
    }

    end {}
}#End New-AsBuiltConfig Function