Yamautomate.Core.psm1

function Get-YcRequiredModules {
    <#
    .SYNOPSIS
    The Get-YcRequiredModules function checks for the availability and import status of a specified PowerShell module and attempts to import it if necessary.
 
    .DESCRIPTION
    The Get-YcRequiredModules function takes the name of several PowerShell modules as input and verifies its installation and import status.
    If the module is not installed, it alerts the user to install it.
    If the module is installed but not imported, the function attempts to import it.
    If importing fails, an error message is displayed.
 
    .PARAMETER moduleNames
    The moduleName parameter is a mandatory array of string specifying the name of the modules to check for.
 
    .INPUTS
    The function does not accept any pipeline input.
 
    .OUTPUTS
    The function outputs a message indicating whether the specified module is installed, imported, or if an error occurred during importing.
 
    .EXAMPLE
    The following example shows how to use the Get-YcRequiredModules function to check and manage the status of a module:
 
    PS> Get-YcRequiredModules -moduleName "Az.Accounts"
    #>


    param (
        [Parameter(Mandatory=$true, Position = 0)] [ValidateNotNullOrEmpty()] [string[]]$moduleNames
    )

    Foreach ($Module in $ModuleNames)
    {
        # Check if the module is installed
        $moduleInstalled = Get-Module -ListAvailable -Name $module

        if (-not $moduleInstalled) 
        {
            $message = "The required module '$module' is not installed. Please install it."
            Write-Output $message -ForegroundColor Yellow
            Throw $message
        }

        # Check if the module is imported
        $moduleImported = Get-Module -Name $module
        if (-not $moduleImported) 
        {
            Write-Output "The required module '$module' is not imported. Trying to import it." -ForegroundColor Yellow

            try {
                Import-Module -Name $module
            } 
            
            catch {
                Write-Error "Could not import module '$module' due to error: $_"
            }
        }
    }
}
function Initialize-YcEventLogging {
    <#
    .SYNOPSIS
    The Initialize-YcEventLogging function sets up event logging by creating a source in the specified log.
 
    .DESCRIPTION
    The Initialize-YcEventLogging function initializes logging by creating an event source in a specified log.
    The function accepts an optional log name, defaulting to "Application", and an optional source name to be associated with the log.
    If the source does not exist, it is created in the specified log.
 
    .PARAMETER logName
    The logName parameter is an optional string specifying the name of the log to associate the source with. It defaults to "Application".
 
    .PARAMETER source
    The source parameter is a mandatory string specifying the source name to be associated with the log.
 
    .INPUTS
    The function does not accept any pipeline input.
 
    .OUTPUTS
    The function does not output anything directly, but it initializes logging by ensuring a source exists.
 
    .EXAMPLE
    The following example shows how to initialize event logging by setting up a source in the "Application" log:
 
    PS> Initialize-YcEventLogging -source "MyAppSource"
    #>


    param (
        [Parameter(Mandatory=$false, Position = 0)] [ValidateNotNullOrEmpty()] [string]$logName = "Application",
        [Parameter(Mandatory=$true, Position = 1)] [string]$source = "YcEventLog"
    )

    if (($IsAdmin = Get-YcCurrentUserType) -eq "Administrator")
    {
        if (![System.Diagnostics.EventLog]::SourceExists($source)) {
            [System.Diagnostics.EventLog]::CreateEventSource($source, $logName)
        }
    }
    else {
        if (![System.Diagnostics.EventLog]::SourceExists($source)) {
            $message = ("Source: "+$source+" does not exist on this machine. Local Administrator privilges required to create the source, but this user context is not an Administrator. Aborting.")
            Write-YcLogMessage -message $message -ToOutput 
            throw $message
        }
    }
}
function Write-YcEventLog {
        <#
    .SYNOPSIS
    The Write-YcEventLog function writes a new event log entry with a specified message and other optional parameters.
 
    .DESCRIPTION
    The Write-YcEventLog function writes a new entry to the specified event log.
    The function accepts a mandatory message parameter and optional parameters for the log name, source, entry type, and event ID.
    The message is processed through the `Protect-LogMessage` function before being logged.
    The default log name is "Application", the default source is "YcEventLogging", and the default entry type is "Information".
 
    .PARAMETER message
    The message parameter is a mandatory string specifying the content of the event log entry.
 
    .PARAMETER logName
    The logName parameter is an optional string specifying the name of the log to write to. The default value is "Application".
 
    .PARAMETER source
    The source parameter is an optional string specifying the source name associated with the log entry.
    The default value is "YcEventLogging".
 
    .PARAMETER entryType
    The entryType parameter is an optional string specifying the type of the log entry.
    The default value is "Information".
 
    .PARAMETER eventId
    The eventId parameter is an optional integer specifying the ID associated with the log entry.
    The default value is 1001.
 
    .INPUTS
    The function does not accept any pipeline input.
 
    .OUTPUTS
    The function does not return any output but writes an event log entry.
 
    .EXAMPLE
    The following example shows how to write a new event log entry:
 
    PS> Write-YcEventLog -message "Application started successfully."
    #>

    param (
        [Parameter(Mandatory=$true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$message,
        [Parameter(Mandatory=$false, Position = 1)] [string]$logName = "Application",
        [Parameter(Mandatory=$false, Position = 2)] [string]$source = "YcEventLog",
        [Parameter(Mandatory=$false, Position = 3)] [ValidateSet("Information", "Warning", "Error")] [string]$entryType = "Information",
        [Parameter(Mandatory=$false, Position = 4)] [int]$eventId = 1001

    )

    $message = Protect-YcLogMessage($message)
    Write-EventLog -LogName $logName -Source $source -EntryType $entryType -EventId $eventId -Message $message
}
function Write-YcLogFile {
    <#
    .SYNOPSIS
    The Write-YcLogFile function writes a message to a specified log file, organizing logs by date and source.
 
    .DESCRIPTION
    The Write-YcLogFile function writes a log message to a specified directory and file.
    It accepts mandatory parameters for the log message, log directory, and source.
    The function checks if the log directory and file exist, creating them if necessary.
    The message is processed through the `Protect-LogMessage` function, and a timestamped log entry is appended to the file, organizing logs by date and source.
    If the log file exceeds a specified size, it is rotated and archived.
 
    .PARAMETER message
    The message parameter is a mandatory string specifying the content of the log entry.
 
    .PARAMETER logDirectory
    The logDirectory parameter is a mandatory string specifying the directory to store log files.
 
    .PARAMETER source
    The source parameter is a mandatory string specifying the source of the log entry.
 
    .PARAMETER maxLogSize
    The maxLogSize parameter is an optional integer specifying the maximum log file size in megabytes before rotating. Default is 5MB.
 
    .PARAMETER archiveDir
    The archiveDir parameter is an optional string specifying the directory to store archived logs. Defaults to "$logDirectory\archive".
 
    .INPUTS
    The function does not accept any pipeline input.
 
    .OUTPUTS
    The function writes a log message to a specified file.
 
    .EXAMPLE
    The following example shows how to write a log message to a specified directory:
 
    PS> Write-YcLogFile -message "Process completed successfully." -logDirectory "C:\Logs" -source "MyApp"
    #>


    param (
        [Parameter(Mandatory=$true, Position=0)] [ValidateNotNullOrEmpty()] [string]$message,
        [Parameter(Mandatory=$true, Position=1)] [ValidateNotNullOrEmpty()] [string]$logDirectory = "$env:USERPROFILE\.yc",
        [Parameter(Mandatory=$true, Position=2)] [ValidateNotNullOrEmpty()] [string]$source,
        [Parameter(Mandatory=$false, Position=3)] [ValidateSet("Information", "Warning", "Error")] [ValidateNotNullOrEmpty()] [string]$entryType = "Information",
        [Parameter(Mandatory=$false, Position=4)] [int]$maxLogSize = 5, # Max log file size in MB
        [Parameter(Mandatory=$false, Position=5)] [string]$archiveDir = "$logDirectory\YC_LogArchive"
    )

    # Ensure the log directory exists, create if not
    if (!(Test-Path $logDirectory)) {
        New-Item -ItemType Directory -Path $logDirectory | Out-Null
    }

    $currentDate = Get-Date -Format "yyyy-MM-dd"
    $LogFilePath = "$logDirectory\YCLog_$source-$currentDate.txt"

    # Check if the log file exists
    if (!(Test-Path $LogFilePath)) {
        New-Item -ItemType File -Path $LogFilePath | Out-Null
    }

    # Rotate the log file if it exceeds maxLogSize
    $fileSizeMB = (Get-Item $LogFilePath).Length / 1MB
    if ($fileSizeMB -ge $maxLogSize) {

        if (!(Test-Path $archiveDir)) {
            New-Item -ItemType Directory -Path $archiveDir | Out-Null
        }

        $archiveFilePath = "$archiveDir\YCLog_$source-$currentDate-$(Get-Date -Format 'HHmmss').txt"
        Move-Item $LogFilePath -Destination $archiveFilePath
        New-Item -ItemType File -Path $LogFilePath | Out-Null
    }

    # Sanitize and prepare the log message
    $message = Protect-YcLogMessage($message)
    $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $LogMessage = $source+" @" +$Timestamp+" "+$entryType+": "+$message

    # Append the log message to the specified file
    Add-Content -Path $LogFilePath -Value $LogMessage
}
function Protect-YcLogMessage {
    <#
    .SYNOPSIS
    The Protect-YcLogMessage function masks sensitive information from a log message.
 
    .DESCRIPTION
    The Protect-YcLogMessage function takes a log message as input and searches for patterns indicative of sensitive information, including client secrets, API keys, bearer tokens, passwords, URLs with queries, and credit card numbers.
    These patterns are masked or redacted to protect sensitive information.
    The function replaces matched patterns with placeholders like "******" or "REDACTED", ensuring the message is safe for logging.
 
    .PARAMETER message
    The message parameter is a mandatory string representing the log message to be processed.
 
    .INPUTS
    The function does not accept any pipeline input.
 
    .OUTPUTS
    The function returns the sanitized log message with sensitive information masked.
 
    .EXAMPLE
    The following example shows how to use the Protect-YcLogMessage function:
 
    PS> Protect-YcLogMessage -message "Bearer token: Bearer abcdefghijklmnopqrs..."
    #>


    param (
        [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$message
    )

    $clientSecretPattern = "\b([a-zA-Z0-9~.]{36,40})\b" 
    $bearerPattern = "Bearer \w+"
    $APIKeyPattern = "APIKey_\w+"
    $URLwithQueryPattern = "https?:\/\/[a-zA-Z0-9-]+\.[a-zA-Z0-9.-]+(\/[a-zA-Z0-9-_]+)*\?[\w=&]+"
    $passwordPattern = "\bPassword\s*:\s*\w+\b"
    $creditCardPattern = "\b(?:\d{4}-?){4,5}\b"

    $patternsToMask = @($clientSecretPattern, $bearerPattern, $APIKeyPattern, $passwordPattern, $creditCardPattern, $URLwithQueryPattern)

    # Replace sensitive patterns with 'REDACTED'
    foreach ($pattern in $patternsToMask) {

        switch ($pattern) {
            $bearerPattern {  
                $message = [regex]::Replace($message, $pattern, {param($m) $m.Value.Substring(0, 6) + "******"})
            }
            $APIKeyPattern {
                $message = [regex]::Replace($message, $pattern, {param($m) $m.Value.Substring(0, 6) + "******"})
            }
            $passwordPattern {
                $message = [regex]::Replace($message, $pattern, {param($m) $m.Value.Substring(0, 6) + "******"})
            }
            $creditCardPattern {
                $message = [regex]::Replace($message, $pattern, {param($m) $m.Value.Substring(0, 6) + "******"})
            }
            $clientSecretPattern {
                $message = [regex]::Replace($message, $pattern, {param($m) $m.Value.Substring(0, 6) + "******"})
            }
            $URLwithQueryPattern {
                $message = [regex]::Replace($message, $pattern, {param($m) $m.Value.Substring(0, 20) + "******"})
            }
            Default {
                $message = [regex]::Replace($message, $pattern, "REDACTED")
            }
        }
    }

    return $message
    
}
function New-YcLogMessage {
    param (
        [string]$CustomText,
        [string]$FunctionName
    )

    if (!($FunctionName))
    {
        $FunctionName = (Get-PSCallStack)[1].FunctionName
    }

    $CurrentDate = Get-Date
    $LogMessage = ($FunctionName+" @ "+$CurrentDate+": "+$CustomText)
    $LogMessage = Protect-YcLogMessage -message $LogMessage

    return $LogMessage
}
function Write-YcLogMessage {
    param (        
    [Parameter(Mandatory=$true, Position=0)] [ValidateNotNullOrEmpty()] [string]$message,
    [Parameter(Mandatory=$false, Position=1)] [ValidateNotNullOrEmpty()] [string]$source = "YcEventLog",
    [Parameter(Mandatory=$false, Position=2)] [ValidateSet("Information", "Warning", "Error")] [ValidateNotNullOrEmpty()] [string]$entryType = "Information",
    [Parameter(Mandatory=$false, Position=3)] [ValidateNotNullOrEmpty()] [bool]$ToEventLog = $false,
    [Parameter(Mandatory=$false, Position=4)] [ValidateNotNullOrEmpty()] [bool]$ToLogFile = $false,
    [Parameter(Mandatory=$false, Position=5)] [ValidateNotNullOrEmpty()] [bool]$ToOutput = $false,
    [Parameter(Mandatory=$false, Position=6)] [ValidateNotNullOrEmpty()] [bool]$WriteHost = $false,
    [Parameter(Mandatory=$false, Position=7)] [ValidateNotNullOrEmpty()] [string]$logDirectory = "$env:USERPROFILE\.yc"
    )

    $FunctionName = (Get-PSCallStack)[1].FunctionName
    $message = New-YcLogMessage -CustomText $message -FunctionName $FunctionName
    
    if ($ToEventLog)
    {
        Initialize-YcEventLogging -source $source
        Write-YcEventLog -message $message -entryType $entryType -source $source 
    }

    If ($ToLogFile)
    {
        Write-YcLogFile -message $message -entryType $entryType -source $source -logDirectory $logDirectory
    }

    If ($ToOutput)
    {
        Write-Output $message
    }

    If ($WriteHost)
    {
        Write-Host $message
    }
}
Function New-YcMgAppReg
{
    param (
        [Parameter(Mandatory=$true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$ApplicationRegistrationDisplayName
    )

    $requiredModules = "Microsoft.Graph.Authentication", "Microsoft.Graph.Applications"
    Get-YcRequiredModules $requiredModules

    try {
        Connect-MgGraph -Scopes "Application.Read.All","Application.ReadWrite.All","User.Read.All"
        $AzureAppRegistration = New-MgApplication -DisplayName $ApplicationRegistrationDisplayName
    }
    catch {
        $Message = "Could not connect to Graph API and or could not create AppRegstistration: " +$ApplicationRegistrationDisplayName+" Error Details: "+$_.Exception.Message
        Write-Host $Message -ForegroundColor Red
    }
    finally {
        Disconnect-MgGraph
    }

    return $AzureAppRegistration
}
function Set-YcMgAppRegCertificate {
    Param(  
    [Parameter(Mandatory=$true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$ApplicationRegistrationDisplayName,  
    [Parameter(Mandatory=$true, Position = 1)] [ValidateNotNullOrEmpty()][string]$CertificateThumbprint
    )  
  
    $requiredModules = "Microsoft.Graph.Authentication", "Microsoft.Graph.Applications"
    Get-YcRequiredModules $requiredModules

    # Load the certificate from the local store
    $LocalCert = Get-Item -Path Cert:\CurrentUser\My\$CertificateThumbprint

    # Create the credential object using the certificate's raw data
    $CertCredential = @{  
        Type = "AsymmetricX509Cert"  
        Usage = "Verify"  
        Key = $LocalCert.RawData  
    }  
    
    try {
        Connect-MgGraph -Scopes "Application.Read.All","Application.ReadWrite.All","User.Read.All"
        $AzureAppRegistration = Get-MgApplication -Filter "DisplayName eq '$($ApplicationRegistrationDisplayName)'"
    
        # Update the application registration with the new key credentials
        Update-MgApplication -ApplicationId $AzureAppRegistration.Id -KeyCredentials @($CertCredential)
    }
    catch {
        $Message = "Could not connect to Graph API and or could not update AppRegstistration: " +$ApplicationRegistrationDisplayName+ "with Certificate: "+$CertificateThumbprint+" Error Details: "+$_.Exception.Message
        Write-Host $Message -ForegroundColor Red
    }
    finally {
        Disconnect-MgGraph      
    }
}  
Function Send-YcMgEmail{
    <#
    .SYNOPSIS
    The Send-YcMgEmail function sends an email using Microsoft Graph.
  
    .DESCRIPTION
    The Send-YcMgEmail function uses Microsoft Graph to send an email.
    It accepts mandatory parameters for the email message, subject, from address, and to address.
    Further mandatory parameters include a client ID, client secret name, and tenant ID for Microsoft Graph authentication.
    The function connects to Microsoft Graph, constructs the email message body, sends the message, and then disconnects from Microsoft Graph.
  
    .PARAMETER clientId
    The clientId parameter is a mandatory string specifying the client ID for Microsoft Graph authentication.
  
    .PARAMETER clientSecretName
    The clientSecretName parameter is a mandatory string specifying the name of the client secret for Microsoft Graph authentication.
  
    .PARAMETER tenantId
    The tenantId parameter is a mandatory string specifying the tenant ID for Microsoft Graph authentication.
  
    .PARAMETER message
    The message parameter is a mandatory string specifying the content of the email.
  
    .PARAMETER subject
    The subject parameter is a mandatory string specifying the subject of the email.
  
    .PARAMETER from
    The from parameter is a mandatory string specifying the sender's address.
  
    .PARAMETER to
    The to parameter is a mandatory string specifying the recipient's address.
  
    .INPUTS
    The function does not accept any pipeline input.
  
    .OUTPUTS
    The function does not output any return value directly but sends an email via Microsoft Graph.
  
    .EXAMPLE
    The following example shows how to send an email:
  
    PS> Send-YcMgEmail -clientId "your-client-id" -clientSecretName "your-client-secret" -tenantId "your-tenant-id" -message "Hello, world!" -subject "Greeting" -from "sender@example.com" -to "recipient@example.com"
  
    #>

    param(
        [Parameter(Mandatory=$true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$clientId,
        [Parameter(Mandatory=$true, Position = 1)] [ValidateNotNullOrEmpty()] [string]$clientSecretName,
        [Parameter(Mandatory=$true, Position = 2)] [ValidateNotNullOrEmpty()] [string]$tenantId,
        [Parameter(Mandatory=$true, Position = 3)] [ValidateNotNullOrEmpty()] [string]$message,
        [Parameter(Mandatory=$true, Position = 4)] [ValidateNotNullOrEmpty()] [string]$subject,
        [Parameter(Mandatory=$true, Position = 5)] [ValidateNotNullOrEmpty()] [string]$from,
        [Parameter(Mandatory=$true, Position = 6)] [ValidateNotNullOrEmpty()] [string]$to
    )

    $messageBody = New-YcMgMailMessageBody -message $message -subject $subject -to $to 
 
    try {
        Connect-MgGraph -ClientId $clientId -ClientSecret $clientSecret -TenantId $tenantId
        New-MgUserMessageSend -UserId $from -BodyParameter $messageBody
    }
    catch {
        $Message = "Could not connect to Graph API and or send E-Mail. Error Details: "+$_.Exception.Message
        Write-Host $Message -ForegroundColor Red
    }
    finally {
        Disconnect-MgGraph
    }
 }
function New-YcMgMailMessageBody {
    <#
    .SYNOPSIS
    The New-YcMgMailMessageBody function constructs an email message body for Microsoft Graph.
  
    .DESCRIPTION
    The New-YcMgMailMessageBody function constructs a dictionary that represents an email message body for Microsoft Graph.
    It accepts mandatory parameters for the message content, subject, and recipient address.
    The function constructs a dictionary with fields for the subject, content type, content, and recipient details, and returns this dictionary as output.
  
    .PARAMETER message
    The message parameter is a mandatory string specifying the content of the email.
  
    .PARAMETER subject
    The subject parameter is a mandatory string specifying the subject of the email.
  
    .PARAMETER to
    The to parameter is a mandatory string specifying the recipient's email address.
  
    .INPUTS
    The function does not accept any pipeline input.
  
    .OUTPUTS
    The function returns a dictionary representing the email message body.
  
    .EXAMPLE
    The following example shows how to construct an email message body:
  
    PS> New-YcMgMailMessageBody -message "Hello, world!" -subject "Greeting" -to "recipient@example.com"
    #>

 
    param (
        [Parameter(Mandatory=$true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$message,
        [Parameter(Mandatory=$true, Position = 1)] [ValidateNotNullOrEmpty()] [string]$subject,
        [Parameter(Mandatory=$true, Position = 2)] [ValidateNotNullOrEmpty()] [string]$to
    )
 
    $message = @{
        Message = @{
            Subject = $subject
            Body = @{
                ContentType = "Text"
                Content = $message
            }
            ToRecipients = @(
                @{
                    EmailAddress = @{
                        Address = $to
                    }
                }
            )
        }
    }
 
    return $message
    
}
function Get-YcCurrentUserType {
 
    param (
        [Parameter(Mandatory=$false, Position = 0)] [ValidateNotNullOrEmpty()] [switch]$LogOutput
    )

    # Check if the current user is an administrator
    $isAdmin = (New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    if ($isAdmin) {
        $UserType = "Administrator"
        if ($LogOutput)
        {
            Write-Host "The current user has administrative privileges." -ForegroundColor Green
        }

    } else {
        $UserType = "User"
        If ($LogOutput)
        {
            Write-Host "The current user does NOT have administrative privileges." -ForegroundColor Yellow
        }
    }
    return $UserType
}
function Convert-YcSecureStringToPlainText {
    <#
    .SYNOPSIS
    The Convert-YcSecureStringToPlainText function converts a SecureString into a plain text string.
 
    .DESCRIPTION
    The Convert-YcSecureStringToPlainText function takes a mandatory SecureString parameter and converts it into a plain text string.
    It uses the System.Net.NetworkCredential class to extract the password value from the SecureString and returns it as a plain text string.
 
    .PARAMETER secureString
    The secureString parameter is a mandatory SecureString that needs to be converted into plain text.
 
    .INPUTS
    The function does not accept any pipeline input.
 
    .OUTPUTS
    The function returns a plain text string representation of the SecureString.
 
    .EXAMPLE
    The following example shows how to convert a SecureString into plain text:
    PS> $secureString = Read-Host "Enter secure text" -AsSecureString
    PS> Convert-YcSecureStringToPlainText -secureString $secureString
 
    #>

    param (
        [Parameter(Mandatory=$true, Position = 0)] [ValidateNotNullOrEmpty()] [Security.SecureString]$secureString
    )

    # Convert the SecureString to a plain text string
    $credential = New-Object System.Net.NetworkCredential("", $secureString)
    $plainTextString = $credential.Password

    return $plainTextString
}
function New-YcRandomPassword {
    <#
    .SYNOPSIS
    The New-YcRandomPassword function generates a random password with a specified length.
 
    .DESCRIPTION
    The New-YcRandomPassword function generates a password of a specified length from a character set that includes uppercase and lowercase letters, digits, and special characters. The default length is 32 characters, but it can be modified by passing a different value to the length parameter.
 
    .PARAMETER length
    The length parameter is an optional integer specifying the desired length of the password. The default value is 32.
 
    .INPUTS
    The function does not accept any pipeline input.
 
    .OUTPUTS
    The function returns a randomly generated password.
 
    .EXAMPLE
    The following example shows how to generate a random password with a default length:
 
    PS> New-YcRandomPassword
    #>

    param (
        [Parameter(Mandatory=$false, Position = 0)] [ValidateNotNullOrEmpty()] [int]$length = 32 # Default length is 32 characters
    )

    $charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%&*-_+-=?"
    $password = -join ((0..($length-1)) | ForEach-Object { $charset[(Get-Random -Maximum $charset.Length)] })
    return $password
}
function New-YcSelfSignedCertForAppReg {
    <#
    .SYNOPSIS
    The New-YcSelfSignedCertForAppReg function creates a self-signed certificate for application registration.
 
    .DESCRIPTION
    The New-YcSelfSignedCertForAppReg function creates a self-signed certificate with a specified subject and validity period, storing it in the user's certificate store.
    It generates a random password for exporting the certificate, exports both a .pfx file (including the private key) and a .cer file (containing only the public key), and returns an object with the certificate's details.
 
    .PARAMETER subject
    The subject parameter is a mandatory string specifying the common name (CN) of the certificate.
 
    .PARAMETER validForYears
    The validForYears parameter is a mandatory string specifying the number of years the certificate is valid for.
 
    .INPUTS
    The function does not accept any pipeline input.
 
    .OUTPUTS
    The function returns an object containing the certificate's thumbprint, file paths, and password.
 
    .EXAMPLE
    The following example shows how to create a self-signed certificate valid for 3 years:
 
    PS> New-YcSelfSignedCertForAppReg -subject "MyApp" -validForYears 3
    #>

    param (
        [Parameter(Mandatory=$true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$subject,
        [Parameter(Mandatory=$true, Position = 1)] [ValidateNotNullOrEmpty()] [string]$validForYears
    )

    # Create the certificate
    $cert = New-SelfSignedCertificate -Subject "CN=$Subject" -CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable -KeySpec Signature -NotAfter (Get-Date).AddYears($validForYears)

    # Extract the thumbprint explicitly
    $thumbprint = $cert.Thumbprint

    # Generate a random password for exporting the certificate
    $password = New-YcRandomPassword
    $securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force

    # Export the certificate to a .pfx file including the private key
    $pfxFilePath = "$env:USERPROFILE\Downloads\AppRegCert.pfx"
    Export-PfxCertificate -Cert "Cert:\CurrentUser\My\$thumbprint" -FilePath $pfxFilePath -Password $securePassword | Out-Null

    # Export the public key only as a .cer file (optional)
    $cerFilePath = "$env:USERPROFILE\Downloads\AppRegCert.cer"
    Get-ChildItem "Cert:\CurrentUser\My\$thumbprint" | Export-Certificate -FilePath $cerFilePath -Force | Out-Null

    # Create a custom object for the output
    $output = New-Object PSObject -Property @{
        "Thumbprint" = $thumbprint
        "CerFilePath" = $cerFilePath
        "Password" = $password
    }

    return $output
}
function Import-YcCertToLocalMachine {
    <#
    .SYNOPSIS
    The Import-YcCertToLocalMachine function imports a certificate into the Local Machine store.
 
    .DESCRIPTION
    The Import-YcCertToLocalMachine function takes a .pfx file path and a secure password, and imports the certificate into the Local Machine store.
    It uses the provided password to unlock the certificate and then stores it in the "Cert:\LocalMachine\My" directory.
    Once the import is complete, a success message is output.
 
    .PARAMETER pfxFilePath
    The pfxFilePath parameter is a mandatory string specifying the path to the .pfx file containing the certificate.
 
    .PARAMETER securePassword
    The securePassword parameter is a mandatory SecureString that provides the password needed to unlock and import the certificate.
 
    .INPUTS
    The function does not accept any pipeline input.
 
    .OUTPUTS
    The function outputs a message indicating the success of the import.
 
    .EXAMPLE
    The following example shows how to import a certificate into the Local Machine store:
 
    PS> Import-YcCertToLocalMachine -pfxFilePath "C:\Downloads\AppRegCert.pfx" -securePassword $password
 
    #>

    param (
        [Parameter(Mandatory=$true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$pfxFilePath,
        [Parameter(Mandatory=$true, Position = 1)] [ValidateNotNullOrEmpty()] [SecureString]$securePassword
    )

    Import-PfxCertificate -FilePath $pfxFilePath -CertStoreLocation "Cert:\LocalMachine\My" -Password $securePassword
    Write-Output "Certificate imported successfully into the Local Machine store."
}
function Get-YcCertificateForAuth {
    param (
        [Parameter(Mandatory=$true)]
        [string]$thumbprint
    )

    # Get the current user
    $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
    Write-Host "Current user: $currentUser" -ForegroundColor Yellow

    # Check if the current user is an administrator
    Get-YcCurrentUserType

    # Define certificate stores to check
    $certStoreLocations = @("LocalMachine\My", "CurrentUser\My")

    $certFound = $false
    $hasAccess = $false

    foreach ($certStoreLocation in $certStoreLocations) {
        # Check if the certificate exists in the current store
        $certificate = Get-ChildItem -Path "Cert:\$certStoreLocation" | Where-Object { $_.Thumbprint -eq $thumbprint }
        if ($certificate) {
            Write-Host "Certificate found in $certStoreLocation store." -ForegroundColor Green
            $certFound = $true

            if ($certificate.HasPrivateKey) {
                Write-Host "The certificate has a private key." -ForegroundColor Green
                
                # Determine key type and get the key container name
                $keyName = $null
                $keyPath = $null
                try {
                    if ($certificate.PrivateKey.CspKeyContainerInfo) {
                        $keyName = $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
                        Write-Host "Key container name (CSP): $keyName" -ForegroundColor Yellow
                    } elseif ($certificate.PrivateKey.Key.UniqueName) {
                        $keyName = $certificate.PrivateKey.Key.UniqueName
                        Write-Host "Key container name (CNG): $keyName" -ForegroundColor Yellow
                    }
                } catch {
                    Write-Host "Failed to retrieve the key container name." -ForegroundColor Red
                }

                if ($keyName) {
                    if ($certStoreLocation -like "LocalMachine\*") {
                        $root = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys"
                        $keyPath = [System.IO.Path]::Combine($root, $keyName)
                    } elseif ($certStoreLocation -like "CurrentUser\*") {
                        $root = [System.IO.Path]::Combine([Environment]::GetFolderPath("ApplicationData"), "Microsoft\Crypto\RSA")
                        $keyPath = Get-ChildItem -Path $root -Recurse | Where-Object { $_.Name -eq $keyName } | Select-Object -First 1
                    }

                    if ($keyPath -and (Test-Path $keyPath)) {
                        Write-Host "Private key file found at: $keyPath" -ForegroundColor Green
                        $hasAccess = $true

                    } else {
                        Write-Host "Private key file not found: $keyPath" -ForegroundColor Red
                        throw "Private key file not found."
                    }
                } else {
                    Write-Host "Could not determine the key container name." -ForegroundColor Red
                    throw "Key container name is not available."
                }
            } else {
                Write-Host "The certificate does not have a private key." -ForegroundColor Red
            }
        } 
    }

    if (-not $certFound) {
        throw "Certificate with thumbprint $thumbprint not found in any specified store."
        }

    return $hasAccess
}
function New-YcSecret {
    <#
  .SYNOPSIS
  The New-YcSecret function stores a secret securely in a specified location.
 
  .DESCRIPTION
  The New-YcSecret function securely stores a secret in one of three locations: Windows Credential Store, Environment Variable, or Azure Key Vault.
  It takes a mandatory secret name and optional parameters to specify the storage location, scope, and Azure Key Vault credentials. Depending on the specified location, the function retrieves necessary modules, gathers credentials, and stores the secret in the chosen manner.
 
  .PARAMETER secretName
  The secretName parameter is a mandatory string specifying the name of the secret.
 
  .PARAMETER SecretLocation
  The SecretLocation parameter is an optional string that indicates where to store the secret: "WindowsCredentialStore", "EnvironmentVariable", or "AzureKeyVault". The default value is "WindowsCredentialStore".
 
  .PARAMETER AzKeyVaultClientId
  The AzKeyVaultClientId parameter is an optional string specifying the client ID for connecting to Azure Key Vault.
 
  .PARAMETER AzKeyVaultTenantId
  The AzKeyVaultTenantId parameter is an optional string specifying the tenant ID for connecting to Azure Key Vault.
 
  .PARAMETER AzKeyVaultName
  The AzKeyVaultName parameter is an optional string specifying the name of the Azure Key Vault.
 
  .PARAMETER scope
  The scope parameter is an optional string that determines the persistence type for Windows Credential Store or Environment Variable.
  It can be "User" or "System-Wide".
 
  .INPUTS
  The function does not accept any pipeline input.
 
  .OUTPUTS
  The function returns a message indicating the success or failure of storing the secret.
 
  .EXAMPLE
  The following example shows how to store a secret in Windows Credential Store:
 
  PS> New-YcSecret -secretName "MyAppSecret"
 
  #>

  param (
      [Parameter(Mandatory=$true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$secretName,   
      [Parameter(Mandatory=$false, Position = 1)] [ValidateSet("WindowsCredentialStore", "EnvironmentVariable", "AzureKeyVault")] [string]$SecretLocation = "WindowsCredentialStore" ,
      [Parameter(Mandatory=$false, Position = 2)] [string]$AzKeyVaultClientId,
      [Parameter(Mandatory=$false, Position = 3)] [string]$AzKeyVaultTenantId,
      [Parameter(Mandatory=$false, Position = 4)] [string]$AzKeyVaultName,
      [Parameter(Mandatory=$false, Position = 5)] [ValidateSet("User", "System-Wide")] [string]$scope = "User"
  )
  switch ($SecretLocation) {
      WindowsCredentialStore 
          {
              $credential = Get-Credential -UserName $secretName -Message "Enter the clientSecret for the App Registration."
              # Check if a non-empty password was provided
              if (-not [string]::IsNullOrEmpty($credential.GetNetworkCredential().Password)) {
                  # Get the password (clientSecret)
                  $Secret = $credential.GetNetworkCredential().Password
          
                  # Determine the persistence type based on the scope parameter
                  switch ($scope) {
                      "User" { $persistenceType = "LocalMachine" }
                      "System-Wide" { $persistenceType = "Enterprise" }
                  }
          
                  New-StoredCredential -Target $secretName -UserName $secretName -Password $Secret -Type Generic -Persist $persistenceType | Out-Null
                  return "Credential saved successfully to Windows Credential Store."
              }
              else {
                  Write-Host "ERROR: There was no clientSecret provided! Run the function again and provide a valid clientSecret!" -ForegroundColor Red
              }
          }

      EnvironmentVariable
          {
              $credential = Get-Credential -UserName $secretName -Message "Enter the credentials to store as Environment Variable"
  
              if (-not [string]::IsNullOrEmpty($credential.GetNetworkCredential().Password)) {
                  # Get the password (clientSecret)
                  $Secret = $credential.GetNetworkCredential().Password
                  Write-Host $secret
          
                  # Determine the persistence type based on the scope parameter
                  switch ($scope) {
                      "User" { $persistenceType = "User" }
                      "System-Wide" { $persistenceType = "Machine" }
                  }

                  [Environment]::SetEnvironmentVariable($SecretName, $Secret, $persistenceType)
                  Write-Host "Credential saved to Environment Variable: $SecretName"
              }
          }
      AzureKeyVault
          {
              Get-YcRequiredModules -moduleName "Az.Accounts"

              Get-YcSecret -WindowsCredentialStore -secretName $AzKeyVaultClientId
              Connect-AzAccount -ServicePrincipal -ApplicationId $AzKeyVaultClientId -TenantId $AzKeyVaultTenantId -Credential (New-Object -TypeName PSCredential -ArgumentList $clientId, $clientSecret)

              $secret = New-AzKeyVaultSecret -VaultName $AzKeyVaultName -Name $secretName -AccountPassword
              
              return "Credential saved to Environment AzureKeyVault: $AzKeyVaultName"

          }
      Default {}
  }
}
function Get-YcSecret {
   <#
  .SYNOPSIS
  The Get-YcSecret function retrieves a stored secret from a specified location.
 
  .DESCRIPTION
  The Get-YcSecret function retrieves a secret from one of three locations: Windows Credential Store, Environment Variable, or Azure Key Vault.
  It takes a mandatory secret name and optional parameters for the storage location, Azure Key Vault credentials, and output format.
  Depending on the specified location, the function retrieves necessary modules, fetches the secret, and returns it, optionally as plain text.
 
  .PARAMETER secretName
  The secretName parameter is a mandatory string specifying the name of the secret to retrieve.
 
  .PARAMETER SecretLocation
  The SecretLocation parameter is an optional string that indicates where to retrieve the secret from: "WindowsCredentialStore", "EnvironmentVariable", or "AzureKeyVault".
  The default value is "WindowsCredentialStore".
 
  .PARAMETER AzKeyVaultClientId
  The AzKeyVaultClientId parameter is an optional string specifying the client ID for connecting to Azure Key Vault.
 
  .PARAMETER AzKeyVaultTenantId
  The AzKeyVaultTenantId parameter is an optional string specifying the tenant ID for connecting to Azure Key Vault.
 
  .PARAMETER AzKeyVaultName
  The AzKeyVaultName parameter is an optional string specifying the name of the Azure Key Vault.
 
  .PARAMETER AzKeyVaultCertThumbprint
  The AzKeyVaultCertThumbprint parameter is an optional string specifying the certificate thumbprint for Azure Key Vault authentication.
 
  .PARAMETER AsPlainText
  The AsPlainText parameter is an optional boolean indicating whether to return the secret as plain text. The default value is false.
 
  .PARAMETER SupressErrors
  The SupressErrors parameter is an optional boolean indicating whether to suppress error messages. The default value is false.
 
  .INPUTS
  The function does not accept any pipeline input.
 
  .OUTPUTS
  The function returns the retrieved secret or an error message.
 
  .EXAMPLE
  The following example shows how to retrieve a secret from the Windows Credential Store:
 
  PS> Get-YcSecret -secretName "MyAppSecret"
 
  #>

  param (
      [Parameter(Mandatory=$true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$secretName,    
      [Parameter(Mandatory=$false, Position = 1)] [ValidateSet("WindowsCredentialStore", "EnvironmentVariable", "AzureKeyVault")] [string]$SecretLocation = "WindowsCredentialStore" ,
      [Parameter(Mandatory=$false, Position = 2)] [string]$AzKeyVaultClientId,
      [Parameter(Mandatory=$false, Position = 3)] [string]$AzKeyVaultTenantId,
      [Parameter(Mandatory=$false, Position = 4)] [string]$AzKeyVaultName,
      [Parameter(Mandatory=$false, Position = 5)] [string]$AzKeyVaultCertThumbprint,
      [Parameter(Mandatory=$false, Position = 6)] [bool]$AsPlainText = $false,
      [Parameter(Mandatory=$false, Position = 7)] [bool]$SupressErrors = $false
  )

  switch ($SecretLocation) {
      WindowsCredentialStore 
      { 
          try 
          {
              $storedCredential = Get-StoredCredential -Target $secretName
      
              # Check if a credential was returned
              if ($null -ne $storedCredential) 
                  {
                      return $storedCredential.Password
                  } 
              else 
                  {
                      $ErrorMessage = "Get-LocalSecret @ "+(Get-Date)+": ERROR: No credential found for the given Name "+$secretName+" Check if the credentialName is correct or if the credential exists in the Windows Credential Store."
                      if ($SupressErrors -eq $false)
                      {
                          Write-Host $ErrorMessage -ForegroundColor Red
                      }
                  }
          }
          catch 
          {
              $ErrorMessage = "Get-LocalSecret @ "+(Get-Date)+": ERROR: Could not retrieve locally stored Secret with name: "+$sercetName+" Error Details: "+$_.Exception.Message
              Write-Host $ErrorMessage -ForegroundColor Red
          }
      }

      EnvironmentVariable 
      {
          If (Test-Path "Env:$secretName")
              {
                  $storedCredential = (Get-Item -Path "Env:$secretName").Value
                  If (-not [string]::IsNullOrEmpty($storedCredential))
                  {
                      return $storedCredential
                  }
                  else {
                      Write-Host "ERROR: Environment variable '$secretName' is null or empty." -ForegroundColor Red
                  }
              }
          else 
          {
              Write-Host "ERROR: Environment variable '$Name' not set." -ForegroundColor Red
          }

      }

      AzureKeyVault
      {
          $requiredModules = "Az.Accounts", "Az.KeyVault"
          Get-YcRequiredModules $requiredModules

          # Checking in LocalMachine store
          $certCheckLocalMachine = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq $AzKeyVaultCertThumbprint  }
          # Checking in CurrentUser store
          $certCheckCurrentUser = Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq $AzKeyVaultCertThumbprint  }

          # If the certificate is not found in either store, throw an exception
          if (-not $certCheckLocalMachine -and -not $certCheckCurrentUser) {
              throw "Certificate with thumbprint $AzKeyVaultCertThumbprint not found in either LocalMachine or CurrentUser store. Aborting."
          }

          try { 
              Write-Host ("Trying to connect to KeyVault: "+$AzKeyVaultName+" from Tenant: "+$AzKeyVaultTenantId+"") -ForegroundColor Yellow
              Write-Host ("Using AppId: "+$AzKeyVaultClientId+"") -ForegroundColor Yellow

              Connect-AzAccount -ApplicationId $AzKeyVaultClientId -CertificateThumbprint $AzKeyVaultCertThumbprint -TenantId $AzKeyVaultTenantId | Out-Null
              Write-Host ("Connected successfully to AzVaultKeyVault: "+$AzKeyVaultName+"") -ForegroundColor Green

              Write-Host ("Grabbing Secret: "+$secretName+"") -ForegroundColor Yellow

              if ($AsPlainText -eq $true)
              {
                  $Secret = Get-AzKeyVaultSecret -VaultName $AzKeyVaultName -Name $secretName -AsPlainText
              }
              else
              {
                  $Secret = Get-AzKeyVaultSecret -VaultName $AzKeyVaultName -Name $secretName
              }  

              if ($Secret -eq $null)
              {
                  Write-Host ("Secret is NULL: "+$secretName+"") -ForegroundColor Red
              }
              else {
                  Write-Host ("Retreieved Secret: "+$secretName+"") -ForegroundColor Green
              }

          }
          catch {
              Write-Host ("Could not connect to Az Account and or retrieve AzVaultSecret: " +$secretName+ "from Vault: "+$AzKeyVaultName+" Error Details: "+$_.Exception.Message) -ForegroundColor Red
              throw
          }
          finally {

              Disconnect-AzAccount | Out-Null
              Write-host ("Successfully disconnected from AzAccount") -ForegroundColor Green
          }

          return $Secret
      }

      Default {}
  }
}
function New-YcGUID {
    $newGUID = [guid]::NewGuid()
    return $newGUID.guid
}
function Get-YcJsonConfig {
    <#
.SYNOPSIS
The Get-YcJsonConfig function retrieves a JSON configuration file from a specified path.
 
.DESCRIPTION
The Get-YcJsonConfig function takes a mandatory path to a JSON configuration file and retrieves its content, converting it into a PowerShell object.
It checks if the specified path exists and attempts to read and convert the content.
If an error occurs, it logs an error message, indicating the failure and its details.
 
.PARAMETER PathToConfig
The PathToConfig parameter is a mandatory string specifying the path to the JSON configuration file.
 
.INPUTS
The function does not accept any pipeline input.
 
.OUTPUTS
The function returns a PowerShell object containing the configuration data or logs an error message.
 
.EXAMPLE
The following example shows how to retrieve a JSON configuration file:
 
PS> Get-YcJsonConfig -PathToConfig "C:\Configs\AppConfig.json"
 
#>

param (
    [Parameter(Mandatory=$true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$PathToConfig
)

if (Test-Path $PathToConfig)
{
    try {
        $config = Get-Content -raw -Path $PathToConfig | ConvertFrom-Json -ErrorAction Stop
        return $config
    }
    catch 
    {
        $ErrorMessage = "Get-JsonConfig @ "+(Get-Date)+": Could not retrieve config from path "+$PathToConfig+" Error Details: "+$_.Exception.Message
        Write-Host $ErrorMessage

    }
}

else
{
    $ErrorMessage = "Get-YcJsonConfig @ "+(Get-Date)+": Config does not exist at path "+$PathToConfig+" Error Details: "+$_.Exception.Message
    Write-Host $ErrorMessage
}
}
function New-YcSampleConfig {
<#
.SYNOPSIS
The New-YcSampleConfig function creates a sample configuration file at a specified path.
 
.DESCRIPTION
The New-YcSampleConfig function takes a mandatory path parameter to specify where to create a sample configuration file.
It defines a sample configuration as a PowerShell hashtable, converts it to a JSON string, and writes it to the specified path.
The configuration includes sections for event logging, Azure Key Vault, Azure General, API settings, solution settings, and notifications.
 
.PARAMETER ConfigPath
The ConfigPath parameter is a mandatory string specifying the path where the configuration file will be created.
 
.INPUTS
The function does not accept any pipeline input.
 
.OUTPUTS
The function writes a sample configuration file to the specified path and logs a success message.
 
.EXAMPLE
The following example shows how to create a sample configuration file:
 
PS> New-YcSampleConfig -ConfigPath "C:\Configs\SampleConfig.json"
 
#>

param (
    [Parameter(Mandatory=$true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$ConfigPath  # Path where the config file will be created
)

<# Define the sample configuration as a PowerShell hashtable
$sampleConfig = @{
    "EventLogging" = @(
        @{
            "NameOfEventSource" = "NameOfSolution"
        }
    )
 
    "AzureKeyVault" = @(
        @{
            "tenantId" = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
            "AzureAppRegistrationClientId" = "xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxxx"
            "KeyVaultName" = "xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxxx"
            "CertificateThumbprint" = "xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxxx"
        }
    )
 
    "AzureGeneral" = @(
        @{
            "tenantId" = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
            "AzureAppRegistrationClientId" = "xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxxx"
        }
    )
 
    "API1" = @(
        @{
            "EndpointURL" = "https://endpointofyourapi.com"
            "APIKeyCredentialName" = "APIKey_AzureDocumentIntelligenceService"
        }
    )
 
    "SteerYourApporSolution" = @(
        @{
            "DirectoryToProcess" = "\\someunc\there"
            "MoveProcessedFilesInto" = "\\anotherunc\there"
        }
    )
 
    "Notifications" = @(
        @{
            "SendReportEmailTo" = "some.recipient@hisdomain.com"
            "SendReportEmailFrom" = "some.sender@hisdomain.com"
        }
    )
}
#>


$configTemplate = [YcConfigTemplate]::new()

# Convert the hashtable to a JSON string
$jsonConfig = $configTemplate.ToJson()

# Write the JSON string to the specified file path
Set-Content -Path $ConfigPath -Value $jsonConfig -Force

$OutputMessage = "Get-New-YcSampleConfig @ "+(Get-Date)+": Sample configuration created successfully at "+$ConfigPath
Write-Host $OutputMessage -ForegroundColor Green
}
class YcConfigTemplate {
    [string]$EventSource = "******"
    [string]$LogEvents = "true"
    [string]$PathToLogFile = "C:\\Temp\\"
    [string]$AzureTenantId = "********-****-****-****-************"
    [string]$KeyVaultName = "***************"
    [string]$AzureAppRegistrationClientId = "********-****-****-****-************"
    [string]$KeyVaultThumbprint = "************************"
    [string]$DocIntelligenceEndpoint = "https://*******.cognitiveservices.azure.com"
    [string]$AKVAPIKeyCredentialName = "***********************"
    [string]$DirToProcess = "\\\\server\\shares\\to\\process"
    [string]$MoveFilesTo = "\\\\server\\shares\\processed"
    [string]$EmailTo = "*****@domain.com"
    [string]$EmailFrom = "*****@domain.com"
    [string]$NotificationsClientId = "********-****-****-****-************"
    [string]$NotificationsClientSecretName = "*********************"

    # Constructor
    YcConfigTemplate() {}

    # Method to convert the class to a hashtable
    [hashtable] ToHashtable() {
        return @{
            "EventLogging" = @(
                @{
                    "LogEvents" = $this.LogEvents
                    "NameOfEventSource" = $this.EventSource
                    "PathToLogFile" = $this.PathToLogFile
                }
            )
            "AzureGeneral" = @(
                @{
                    "tenantId" = $this.AzureTenantId
                }
            )
            "AzureKeyVault" = @(
                @{
                    "tenantId" = $this.AzureTenantId
                    "KeyVaultName" = $this.KeyVaultName
                    "AzureAppRegistrationClientId" = $this.AzureAppRegistrationClientId
                    "CertificateThumbprint" = $this.KeyVaultThumbprint
                }
            )
            "AzureDocumentIntelligenceService" = @(
                @{
                    "EndpointURL" = $this.DocIntelligenceEndpoint
                    "AKVAPIKeyCredentialName" = $this.AKVAPIKeyCredentialName
                }
            )
            "ProductionPaperMoverConfig" = @(
                @{
                    "DirectoryToProcess" = $this.DirToProcess
                    "MoveProcessedFilesInto" = $this.MoveFilesTo
                }
            )
            "Notifications" = @(
                @{
                    "SendReportEmailTo" = $this.EmailTo
                    "SendReportEmailFrom" = $this.EmailFrom
                    "AzureAppRegistrationClientId" = $this.NotificationsClientId
                    "AzureAppRegistrationClientSecretCredentialName" = $this.NotificationsClientSecretName
                }
            )
        }
    }

    # Method to convert the class to a JSON string
    [string] ToJson() {
        $config = $this.ToHashtable()
        return $config | ConvertTo-Json -Depth 4
    }
}