EnhancedPSADTAO.psm1

#Region '.\Public\Create-LocalAdminAccount.ps1' -1

function Create-LocalAdminAccount {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Username,

        [Parameter(Mandatory = $true)]
        [string]$Password
    )

    begin {
        Write-EnhancedLog -Message 'Starting Create-LocalAdminAccount function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            # Check if the user already exists
            $userExists = Get-LocalUser -Name $Username -ErrorAction SilentlyContinue

            if (-not $userExists) {
                # Create the user account
                $securePassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
                $userParams = @{
                    Name                 = $Username
                    Password             = $securePassword
                    FullName             = "FC Remove Account"
                    Description          = "Account used for FC removal process"
                    PasswordNeverExpires = $true
                    AccountNeverExpires  = $true
                }
                New-LocalUser @userParams
                Write-EnhancedLog -Message "Local administrator account '$Username' created." -Level 'INFO'
            } else {
                Write-EnhancedLog -Message "Local administrator account '$Username' already exists." -Level 'WARNING'
            }

            # Check if the user is already a member of the local Administrators group
            $group = Get-LocalGroup -Name "Administrators"
            $memberExists = $null
            try {
                $memberExists = $group | Get-LocalGroupMember | Where-Object { $_.Name -eq $Username }
            } catch {
                Write-EnhancedLog -Message "Failed to retrieve group members: $_" -Level 'ERROR'
            }

            if (-not $memberExists) {
                # Add the user to the local Administrators group
                $groupParams = @{
                    Group  = "Administrators"
                    Member = $Username
                }
                try {
                    Add-LocalGroupMember @groupParams
                    Write-EnhancedLog -Message "User '$Username' added to the Administrators group." -Level 'INFO'
                } catch [Microsoft.PowerShell.Commands.AddLocalGroupMemberCommand+MemberExistsException] {
                    Write-EnhancedLog -Message "User '$Username' is already a member of the Administrators group." -Level 'WARNING'
                }
            } else {
                Write-EnhancedLog -Message "User '$Username' is already a member of the Administrators group." -Level 'WARNING'
            }

        } catch {
            Write-EnhancedLog -Message "An error occurred while creating the local admin account or adding to Administrators group: $_" -Level 'ERROR'
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Create-LocalAdminAccount function completed' -Level 'INFO'
    }
}

# # Define parameters for creating the local admin account
# $localAdminParams = @{
# Username = "fcremove"
# Password = "fcremove"
# }

# # Create the local admin account
# Create-LocalAdminAccount @localAdminParams
#EndRegion '.\Public\Create-LocalAdminAccount.ps1' 82
#Region '.\Public\Detect-BitLockerStatus.ps1' -1

function Detect-BitLockerStatus {
    [CmdletBinding()]
    param (
        [string[]]$DriveLetters = @("C:")
    )

    begin {
        Write-EnhancedLog -Message 'Starting Detect-BitLockerStatus function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        foreach ($drive in $DriveLetters) {
            try {
                $bitLockerStatus = Get-BitLockerVolume -MountPoint $drive

                if ($bitLockerStatus) {
                    $protectionStatus = $bitLockerStatus.ProtectionStatus
                    Write-EnhancedLog -Message "BitLocker status for drive $drive $protectionStatus" -Level 'INFO'
                    Write-Output "BitLocker status for drive $drive $protectionStatus"
                } else {
                    Write-EnhancedLog -Message "BitLocker status not found for drive $drive" -Level 'WARNING'
                    Write-Output "BitLocker status not found for drive $drive"
                }
            } catch {
                Handle-Error -ErrorRecord $_
            }
        }
    }

    end {
        Write-EnhancedLog -Message 'Detect-BitLockerStatus function completed' -Level 'INFO'
    }
}

# # Example usage of Detect-BitLockerStatus function with splatting
# $params = @{
# DriveLetters = @("C:", "D:")
# }

# # Call the Detect-BitLockerStatus function using splatting
# Detect-BitLockerStatus @params
#EndRegion '.\Public\Detect-BitLockerStatus.ps1' 43
#Region '.\Public\Detect-FortiClientEMSInstallation.ps1' -1

function Detect-FortiClientEMSInstallation {
    <#
    .SYNOPSIS
    Checks for FortiClientEMS installation and version.
    .PARAMETER RegistryPaths
    An array of registry paths to check.
    .PARAMETER SoftwareName
    The name of the software to search for.
    .PARAMETER ExcludedVersion
    The version of the software to exclude.
    .OUTPUTS
    A hashtable indicating whether the software is installed and its version.
    #>

    [CmdletBinding()]
    param (
        [string[]]$RegistryPaths,
        [string]$SoftwareName,
        [version]$ExcludedVersion
    )

    foreach ($path in $RegistryPaths) {
        $items = Get-ChildItem -Path $path -ErrorAction SilentlyContinue

        foreach ($item in $items) {
            $app = Get-ItemProperty -Path $item.PsPath -ErrorAction SilentlyContinue
            if ($app.DisplayName -like "*$SoftwareName*") {
                $installedVersion = New-Object Version $app.DisplayVersion
                if ($installedVersion -lt $ExcludedVersion) {
                    return @{
                        IsInstalled = $true
                        Version = $app.DisplayVersion
                        ProductCode = $app.PSChildName
                    }
                }
            }
        }
    }

    return @{IsInstalled = $false}
}
#EndRegion '.\Public\Detect-FortiClientEMSInstallation.ps1' 41
#Region '.\Public\Detect-SystemMode.ps1' -1

function Detect-SystemMode {
    [CmdletBinding()]
    param (
        [string]$RegistryPath
    )

    begin {
        Write-EnhancedLog -Message 'Starting Detect-SystemMode function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            $safeMode = Get-ItemProperty -Path $RegistryPath -ErrorAction Stop

            if ($safeMode.Option -eq 1) {
                Write-EnhancedLog -Message "System is in Safe Mode" -Level 'INFO'
                $Global:SystemMode = "Safe Mode"
            } else {
                Write-EnhancedLog -Message "System is in Normal Mode" -Level 'INFO'
                $Global:SystemMode = "Normal Mode"
            }
        } catch {
            Write-EnhancedLog -Message "System is in Normal Mode (SafeBoot key not found)" -Level 'INFO'
            $Global:SystemMode = "Normal Mode"
        }
    }

    end {
        Write-EnhancedLog -Message 'Detect-SystemMode function completed' -Level 'INFO'
    }
}

# # Example usage of Detect-SystemMode function with splatting
# $params = @{
# RegistryPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\SafeBoot\Option'
# }

# # Call the Detect-SystemMode function using splatting
# Detect-SystemMode @params

# Access the result
# $SystemMode
#EndRegion '.\Public\Detect-SystemMode.ps1' 44
#Region '.\Public\Disable-CustomBitLocker-Archive.ps1' -1

# function Disable-CustomBitLocker {
# [CmdletBinding()]
# param (
# [string[]]$DriveLetters = @("C:")
# )

# begin {
# Write-EnhancedLog -Message 'Starting Disable-CustomBitLocker function' -Level 'INFO'
# Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# }

# process {
# # Detect BitLocker status for the provided drives
# $bitLockerStatusResults = Detect-BitLockerStatus -DriveLetters $DriveLetters

# foreach ($status in $bitLockerStatusResults) {
# $drive = $status.MountPoint
# $protectionStatus = $status.ProtectionStatus

# if ($protectionStatus -eq "On") {
# try {
# Write-EnhancedLog -Message "Disabling BitLocker on drive $drive" -Level 'INFO'
# Disable-BitLocker -MountPoint $drive -RebootCount 0 -Wait

# Write-EnhancedLog -Message "BitLocker disabled on drive $drive" -Level 'INFO'
# Write-Output "BitLocker disabled on drive $drive"
# } catch {
# Handle-Error -ErrorRecord $_
# }
# } else {
# Write-EnhancedLog -Message "BitLocker is not enabled on drive $drive" -Level 'INFO'
# Write-Output "BitLocker is not enabled on drive $drive"
# }
# }
# }

# end {
# Write-EnhancedLog -Message 'Disable-CustomBitLocker function completed' -Level 'INFO'
# }
# }

# # # Example usage of Disable-CustomBitLocker function with splatting
# # $params = @{
# # DriveLetters = @("C:", "D:")
# # }

# # # Call the Disable-CustomBitLocker function using splatting
# # Disable-CustomBitLocker @params
#EndRegion '.\Public\Disable-CustomBitLocker-Archive.ps1' 49
#Region '.\Public\Enter-SafeModeBasedOnDetection.ps1' -1

function Enter-SafeModeBasedOnDetection {
    [CmdletBinding()]
    param (
        [string]$RegistryPath,
        [string]$BCDeditPath,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message 'Starting Enter-SafeModeBasedOnDetection function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            # Detect system mode
            $params = @{
                RegistryPath = $RegistryPath
            }
            Detect-SystemMode @params

            if ($Global:SystemMode -eq "Normal Mode") {
                # Construct the arguments
                $arguments = $ArgumentTemplate

                # Execute the bcdedit command to enable Safe Mode
                Write-EnhancedLog -Message "Executing bcdedit with arguments: $arguments" -Level 'INFO'
                Start-Process -FilePath $BCDeditPath -ArgumentList $arguments -Wait

                Write-EnhancedLog -Message 'Successfully set the system to boot into Safe Mode on next restart' -Level 'INFO'
            } else {
                Write-EnhancedLog -Message 'System is already in Safe Mode' -Level 'INFO'
            }
        } catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Enter-SafeModeBasedOnDetection function completed' -Level 'INFO'
    }
}

# # Example usage of Enter-SafeModeBasedOnDetection function with splatting
# $params = @{
# RegistryPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\SafeBoot\Option'
# BCDeditPath = 'bcdedit.exe'
# ArgumentTemplate = '/set {current} safeboot minimal'
# }

# # Call the Enter-SafeModeBasedOnDetection function using splatting
# Enter-SafeModeBasedOnDetection @params
#EndRegion '.\Public\Enter-SafeModeBasedOnDetection.ps1' 53
#Region '.\Public\Exit-SafeModeBasedOnDetection.ps1' -1

function Exit-SafeModeBasedOnDetection {
    [CmdletBinding()]
    param (
        [string]$RegistryPath,
        [string]$BCDeditPath,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message 'Starting Exit-SafeModeBasedOnDetection function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            # Detect system mode
            $params = @{
                RegistryPath = $RegistryPath
            }
            Detect-SystemMode @params

            if ($Global:SystemMode -eq "Safe Mode") {
                # Construct the arguments
                $arguments = $ArgumentTemplate

                # Execute the bcdedit command to disable Safe Mode
                Write-EnhancedLog -Message "Executing bcdedit with arguments: $arguments" -Level 'INFO'
                Start-Process -FilePath $BCDeditPath -ArgumentList $arguments -Wait

                Write-EnhancedLog -Message 'Successfully set the system to boot into Normal Mode on next restart' -Level 'INFO'
            } else {
                Write-EnhancedLog -Message 'System is already in Normal Mode' -Level 'INFO'
            }
        } catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Exit-SafeModeBasedOnDetection function completed' -Level 'INFO'
    }
}

# # Example usage of Exit-SafeModeBasedOnDetection function with splatting
# $params = @{
# RegistryPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\SafeBoot\Option'
# BCDeditPath = 'bcdedit.exe'
# ArgumentTemplate = '/deletevalue {current} safeboot'
# }

# # Call the Exit-SafeModeBasedOnDetection function using splatting
# Exit-SafeModeBasedOnDetection @params
#EndRegion '.\Public\Exit-SafeModeBasedOnDetection.ps1' 53
#Region '.\Public\Export-RegistryKeys.ps1' -1

function Export-RegistryKeys {
    [CmdletBinding()]
    param (
        [string]$ScriptDirectory,
        [string]$RegistryKeyPath
    )

    begin {
        Write-EnhancedLog -Message 'Starting Export-RegistryKeys function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            if (-not (Test-Path $ScriptDirectory)) {
                Write-EnhancedLog -Message "Script directory not found: $ScriptDirectory" -Level 'ERROR'
                return
            }

            $timestamp = (Get-Date).ToString("yyyyMMddHHmmss")
            $exportFilePath = Join-Path -Path $ScriptDirectory -ChildPath "RegistryExport_$timestamp.reg"

            $arguments = "export `"$RegistryKeyPath`" `"$exportFilePath`" /y"
            $startProcessParams = @{
                FilePath = "reg.exe"
                ArgumentList = $arguments
                Wait = $true
            }

            Write-EnhancedLog -Message "Exporting registry key: $RegistryKeyPath to file: $exportFilePath" -Level 'INFO'
            
            Start-Process @startProcessParams

            if (Test-Path $exportFilePath) {
                Write-EnhancedLog -Message "Registry key export completed successfully: $exportFilePath" -Level 'INFO'
                
                # Validate the exported registry keys
                $validateParams = @{
                    RegistryFilePath = $exportFilePath
                }
                Validate-RegistryKeys @validateParams
            } else {
                Write-EnhancedLog -Message "Failed to export registry key: $RegistryKeyPath" -Level 'ERROR'
            }
        }
        catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Export-RegistryKeys function completed' -Level 'INFO'
    }
}

# # Usage Example with Splatting
# $scriptDirectory = "C:\Path\To\Your\Export\Directory"
# $params = @{
# ScriptDirectory = $scriptDirectory
# RegistryKeyPath = 'HKEY_LOCAL_MACHINE\SOFTWARE\Fortinet\FortiClient\Sslvpn\Tunnels'
# }
# Export-RegistryKeys @params
#EndRegion '.\Public\Export-RegistryKeys.ps1' 63
#Region '.\Public\Find-UninstallString.ps1' -1

       # Function to find the uninstall string from the registry
       function Find-UninstallString {
        param (
            [string[]]$UninstallKeys,
            [string]$ApplicationName
        )

        try {
            foreach ($key in $UninstallKeys) {
                $items = Get-ChildItem -Path $key -ErrorAction SilentlyContinue
                foreach ($item in $items) {
                    $app = Get-ItemProperty -Path $item.PsPath
                    if ($app.DisplayName -like $ApplicationName) {
                        Write-EnhancedLog -Message "Found application: $($app.DisplayName) with product ID: $($app.PSChildName)" -Level 'INFO'
                        return $app.PSChildName.Trim('{}')
                    }
                }
            }
            Write-EnhancedLog -Message "No matching application found for: $ApplicationName" -Level 'WARNING'
        } catch {
            Handle-Error -ErrorRecord $_
        }
        return $null
    }
#EndRegion '.\Public\Find-UninstallString.ps1' 25
#Region '.\Public\Import-FortiClientConfig.ps1' -1

function Import-FortiClientConfig {
    [CmdletBinding()]
    param (
        [string]$ScriptRoot,
        [string]$FortiClientPath,
        [string]$ConfigFileExtension,
        [string]$FCConfigExecutable,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message 'Starting Import-FortiClientConfig function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            # Find the configuration file in the root of the script directory
            $xmlConfigFile = Get-ChildItem -Path $ScriptRoot -Filter $ConfigFileExtension | Select-Object -First 1

            if (-not $xmlConfigFile) {
                Write-EnhancedLog -Message "No configuration file found in the script directory: $ScriptRoot" -Level 'ERROR'
                Write-Output "No configuration file found in the script directory: $ScriptRoot"
                return
            }

            # Check if the FortiClient directory exists
            if (-not (Test-Path -Path $FortiClientPath)) {
                Write-EnhancedLog -Message "FortiClient directory not found at path: $FortiClientPath" -Level 'ERROR'
                Write-Output "FortiClient directory not found at path: $FortiClientPath"
                return
            }

            # Set location to FortiClient directory
            Set-Location -Path $FortiClientPath

            # Execute the FCConfig.exe with the specified arguments
            $fcConfigPath = Join-Path -Path $FortiClientPath -ChildPath $FCConfigExecutable
            $arguments = $ArgumentTemplate -replace '{ConfigFilePath}', $xmlConfigFile.FullName
            Start-Process -FilePath $fcConfigPath -ArgumentList $arguments -Wait

            Write-EnhancedLog -Message 'FCConfig process completed' -Level 'INFO'
            Write-Output "FCConfig process completed"
        } catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Import-FortiClientConfig function completed' -Level 'INFO'
    }
}


# # Example usage of Import-FortiClientConfig function with splatting
# $importParams = @{
# ScriptRoot = $PSScriptRoot
# FortiClientPath = "C:\Program Files\Fortinet\FortiClient"
# ConfigFileExtension = "*.xml"
# FCConfigExecutable = "FCConfig.exe"
# ArgumentTemplate = "-m all -f `{ConfigFilePath}` -o import -i 1"
# }

# # Call the Import-FortiClientConfig function using splatting
# Import-FortiClientConfig @importParams
#EndRegion '.\Public\Import-FortiClientConfig.ps1' 66
#Region '.\Public\Import-RegistryFilesInScriptRoot.ps1' -1

function Import-RegistryFilesInScriptRoot {
    [CmdletBinding()]
    param (
        [string]$Filter,
        [string]$FilePath,
        [string]$Arguments,
        $scriptDirectory
    )

    begin {
        Write-EnhancedLog -Message 'Starting Import-RegistryFilesInScriptRoot function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            $registryFiles = Get-ChildItem -Path $scriptDirectory -Filter $Filter

            if ($registryFiles.Count -eq 0) {
                Write-EnhancedLog -Message "No registry files found in the directory: $scriptDirectory" -Level 'WARNING'
                return
            }

            foreach ($registryFile in $registryFiles) {
                $registryFilePath = $registryFile.FullName

                if (Test-Path $registryFilePath) {
                    Write-EnhancedLog -Message "Found registry file: $registryFilePath" -Level 'INFO'
                    $startProcessParams = @{
                        FilePath = $FilePath
                        ArgumentList = $Arguments
                        Wait = $true
                    }
                    Start-Process @startProcessParams
                    Write-EnhancedLog -Message "Registry file import process completed for: $registryFilePath" -Level 'INFO'

                    # Validate the registry keys
                    Validate-RegistryKeys -RegistryFilePath $registryFilePath
                }
                else {
                    Write-EnhancedLog -Message "Registry file not found at path: $registryFilePath" -Level 'ERROR'
                }
            }
        }
        catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Import-RegistryFilesInScriptRoot function completed' -Level 'INFO'
    }
}


# # Define parameters for splatting
# $params = @{
# Filter = "*.reg"
# FilePath = "reg.exe"
# Args = "import `"$registryFilePath`""
# }

# # Call the Import-RegistryFilesInScriptRoot function using splatting
# Import-RegistryFilesInScriptRoot @params
#EndRegion '.\Public\Import-RegistryFilesInScriptRoot.ps1' 65
#Region '.\Public\Install-MSIPackage.ps1' -1

function Install-MsiPackage {
    [CmdletBinding()]
    param (
        [string]$ScriptRoot,
        [string]$MsiFileName,
        [string]$FilePath,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message "Starting Install-MsiPackage function for: $MsiFileName" -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            $installerPath = Join-Path -Path $ScriptRoot -ChildPath $MsiFileName
            
            if (Test-Path $installerPath) {
                Write-EnhancedLog -Message "Found installer file: $installerPath" -Level 'INFO'
                $arguments = $ArgumentTemplate -replace '{InstallerPath}', $installerPath
                Start-Process -FilePath $FilePath -ArgumentList $arguments -Wait
                Write-EnhancedLog -Message "Installation process completed for: $installerPath" -Level 'INFO'
                Write-EnhancedLog -Message "Installation process completed for: $installerPath" -Level 'INFO'
            } else {
                Write-EnhancedLog -Message "Installer file not found at path: $installerPath.. proceeding to extract ZIP files" -Level 'WARNING'

                Write-EnhancedLog -Message "Extracting all ZIP files recursively..."
                $zipFiles = Get-ChildItem -Path $ScriptRoot -Recurse -Include '*.zip.001'
                foreach ($zipFile in $zipFiles) {
                    $destinationFolder = [System.IO.Path]::GetDirectoryName($zipFile.FullName)
                    Write-EnhancedLog -Message "Combining and extracting segmented ZIP files for $($zipFile.BaseName) using 7-Zip..."
                    $sevenZipCommand = "& `"$env:ProgramFiles\7-Zip\7z.exe`" x `"$zipFile`" -o`"$destinationFolder`""
                    Write-EnhancedLog -Message "Executing: $sevenZipCommand"
                    Invoke-Expression $sevenZipCommand
                }
                Write-EnhancedLog -Message "All ZIP files extracted."

                $arguments = $ArgumentTemplate -replace '{InstallerPath}', $installerPath
                Start-Process -FilePath $FilePath -ArgumentList $arguments -Wait


                # Write-Output "Installer file not found at path: $installerPath"
            }
        } catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Install-MsiPackage function completed' -Level 'INFO'
    }
}

# # Example usage of Install-MsiPackage function with splatting
# $params = @{
# ScriptRoot = $PSScriptRoot
# MsiFileName = 'FortiClient.msi'
# FilePath = 'MsiExec.exe'
# ArgumentTemplate = "/i `{InstallerPath}` /quiet /norestart"
# }
# Install-MsiPackage @params
#EndRegion '.\Public\Install-MSIPackage.ps1' 63
#Region '.\Public\Invoke-Uninstall.ps1' -1

function Invoke-Uninstall {
    [CmdletBinding()]
    param (
        [string]$ProductId,
        [string]$FilePath,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message 'Starting Invoke-Uninstall function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            Write-EnhancedLog -Message 'Starting uninstallation process.' -Level 'INFO'

            # Ensure the ProductId is wrapped in curly braces
            $wrappedProductId = "{$ProductId}"
            # Construct the argument list using the template
            $arguments = $ArgumentTemplate -replace '{ProductId}', $wrappedProductId

            Write-EnhancedLog -Message "FilePath: $FilePath" -Level 'INFO'
            Write-EnhancedLog -Message "Arguments: $arguments" -Level 'INFO'

            Start-Process -FilePath $FilePath -ArgumentList $arguments -Wait -WindowStyle Hidden

            Write-EnhancedLog -Message "Executed uninstallation with arguments: $arguments" -Level 'INFO'
        } catch {
            Write-EnhancedLog -Message "An error occurred during the uninstallation process: $($_.Exception.Message)" -Level 'ERROR'
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Invoke-Uninstall function completed' -Level 'INFO'
    }
}

# # Example usage of Invoke-Uninstall function with splatting
# $params = @{
# ProductId = '0DC51760-4FB7-41F3-8967-D3DEC9D320EB'
# FilePath = 'MsiExec.exe'
# ArgumentTemplate = "/X{ProductId} /quiet /norestart"
# }
# Invoke-Uninstall @params
#EndRegion '.\Public\Invoke-Uninstall.ps1' 47
#Region '.\Public\Remove-Autologin.ps1' -1

function Remove-AutoLogin {
    [CmdletBinding()]

    param ()

    begin {
        Write-EnhancedLog -Message 'Starting Remove-AutoLogin function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            # Check and remove the auto-login registry keys if they exist
            $keysToRemove = @("AutoAdminLogon", "DefaultUserName", "DefaultPassword", "DefaultDomainName")
            foreach ($key in $keysToRemove) {
                $keyPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\$key"
                if (Test-Path -Path $keyPath) {
                    Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name $key -Force
                    Write-EnhancedLog -Message "Removed auto-login key '$key'." -Level 'INFO'
                } else {
                    Write-EnhancedLog -Message "Auto-login key '$key' does not exist." -Level 'INFO'
                }
            }

            Write-EnhancedLog -Message "Auto-login settings removed." -Level 'INFO'
        } catch {
            Write-EnhancedLog -Message "An error occurred while removing auto-login settings: $_" -Level 'ERROR'
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Remove-AutoLogin function completed' -Level 'INFO'
    }
}

# Example usage:
# Remove-AutoLogin
#EndRegion '.\Public\Remove-Autologin.ps1' 39
#Region '.\Public\Remove-FortiSoftware.ps1' -1

function Remove-FortiSoftware {
    [CmdletBinding()]
    param (
        [string]$ScriptRoot,
        [string]$SoftwareName,
        [string]$MsiZapFileName,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message 'Starting Remove-FortiSoftware function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            $msiZapPath = Join-Path -Path $ScriptRoot -ChildPath $MsiZapFileName

            if (Test-Path $msiZapPath) {
                $identifyingNumber = Get-CimInstance -ClassName Win32_Product | Where-Object { $_.Name -like $SoftwareName } | Select-Object -ExpandProperty IdentifyingNumber

                if ($identifyingNumber) {
                    Write-EnhancedLog -Message "Found software with IdentifyingNumber: $identifyingNumber" -Level 'INFO'
                    Write-EnhancedLog -Message "Executing MsiZap with IdentifyingNumber: $identifyingNumber" -Level 'INFO'

                    $argumentList = $ArgumentTemplate -replace '{IdentifyingNumber}', $identifyingNumber
                    Start-Process -FilePath $msiZapPath -ArgumentList $argumentList -Verb RunAs -Wait

                    Write-EnhancedLog -Message 'MsiZap process completed' -Level 'INFO'
                } else {
                    Write-EnhancedLog -Message 'No matching software found' -Level 'WARNING'
                }
            } else {
                Write-EnhancedLog -Message "MsiZap.exe not found at path: $msiZapPath" -Level 'ERROR'
            }
        } catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Remove-FortiSoftware function completed' -Level 'INFO'
    }
}

# # Example usage of Remove-FortiSoftware function with splatting
# $params = @{
# ScriptRoot = $PSScriptRoot
# SoftwareName = '*forti*'
# MsiZapFileName = 'MsiZap.Exe'
# ArgumentTemplate= 'TW! {IdentifyingNumber}'
# }
# Remove-FortiSoftware @params
#EndRegion '.\Public\Remove-FortiSoftware.ps1' 54
#Region '.\Public\Remove-RegistryPath.ps1' -1

function Remove-RegistryPath {
    param (
        [string]$RegistryPath
    )

    Write-EnhancedLog -Message "Starting Remove-RegistryPath function for: $RegistryPath" -Level 'INFO'

    try {
        if (Test-Path -Path "Registry::$RegistryPath") {
            Remove-Item -Path "Registry::$RegistryPath" -Recurse -Force
            Write-EnhancedLog -Message "Successfully removed registry path: $RegistryPath" -Level 'INFO'
            Write-Output "Successfully removed registry path: $RegistryPath"
        } else {
            Write-EnhancedLog -Message "Registry path not found: $RegistryPath" -Level 'WARNING'
            Write-Output "Registry path not found: $RegistryPath"
        }
    } catch {
        Handle-Error -ErrorRecord $_
    } finally {
        Write-EnhancedLog -Message 'Remove-RegistryPath function completed' -Level 'INFO'
    }
}
#EndRegion '.\Public\Remove-RegistryPath.ps1' 23
#Region '.\Public\Suspend-BitLockerForDrives.ps1' -1

function Suspend-BitLockerForDrives {
    [CmdletBinding()]
    param (
        [string[]]$DriveLetters
    )

    begin {
        Write-EnhancedLog -Message 'Starting Suspend-BitLockerForDrives function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        # Detect BitLocker status for the provided drives
        $bitLockerStatusResults = Detect-BitLockerStatus -DriveLetters $DriveLetters

        foreach ($status in $bitLockerStatusResults) {
            $drive = $status.MountPoint
            $protectionStatus = $status.ProtectionStatus

            if ($protectionStatus -eq "On") {
                try {
                    Write-EnhancedLog -Message "Suspending BitLocker on drive $drive" -Level 'INFO'
                    Suspend-BitLocker -MountPoint $drive -RebootCount 0

                    Write-EnhancedLog -Message "BitLocker suspended on drive $drive" -Level 'INFO'
                } catch {
                    Handle-Error -ErrorRecord $_
                }
            } else {
                Write-EnhancedLog -Message "BitLocker is not enabled on drive $drive" -Level 'INFO'
            }
        }
    }

    end {
        Write-EnhancedLog -Message 'Suspend-BitLockerForDrives function completed' -Level 'INFO'
    }
}

# # Example usage of Suspend-BitLockerForDrives function with splatting
# $params = @{
# DriveLetters = @("C:", "D:")
# }

# # Call the Suspend-BitLockerForDrives function using splatting
# Suspend-BitLockerForDrives @params
#EndRegion '.\Public\Suspend-BitLockerForDrives.ps1' 47
#Region '.\Public\Uninstall-FortiClientEMSAgentApplication.ps1' -1

function Uninstall-FortiClientEMSAgentApplication {
    [CmdletBinding()]
    param (
        [string[]]$UninstallKeys,
        [string]$ApplicationName,
        [string]$FilePath,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message 'Starting the Uninstall-FortiClientEMSAgentApplication function...' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            $findParams = @{
                UninstallKeys  = $UninstallKeys
                ApplicationName = $ApplicationName
            }
            $productId = Find-UninstallString @findParams

            if ($null -ne $productId) {
                Write-EnhancedLog -Message "Found product ID: $productId" -Level 'INFO'
                
                # Prepare parameters for Invoke-Uninstall
                $invokeParams = @{
                    ProductId        = $productId
                    FilePath         = $FilePath
                    ArgumentTemplate = $ArgumentTemplate
                }
                Invoke-Uninstall @invokeParams
                #wait a bit before going into detection/validation
                Start-Sleep -Seconds 30
            } else {
                Write-EnhancedLog -Message 'Product ID not found for FortiClientEMSAgent application.' -Level 'WARNING'
            }
        } catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Uninstall process completed.' -Level 'INFO'
    }
}

# # Example usage of Uninstall-FortiClientEMSAgentApplication function with splatting
# $params = @{
# UninstallKeys = @(
# 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
# 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
# )
# ApplicationName = '*Forti*'
# FilePath = 'MsiExec.exe'
# ArgumentTemplate = "/X{ProductId} /quiet /norestart"
# }
# Uninstall-FortiClientEMSAgentApplication @params
#EndRegion '.\Public\Uninstall-FortiClientEMSAgentApplication.ps1' 59
#Region '.\Public\Validate-RegistryKeys.ps1' -1

function Validate-RegistryKeys {
    param (
        [string]$RegistryFilePath
    )

    Write-EnhancedLog -Message "Starting Validate-RegistryKeys function for: $RegistryFilePath" -Level 'INFO'

    try {
        $importedKeys = Get-Content -Path $RegistryFilePath | Where-Object { $_ -match '^\[.*\]$' } | ForEach-Object { $_ -replace '^\[|\]$', '' }
        $importSuccess = $true

        foreach ($key in $importedKeys) {
            if (Test-Path -Path "Registry::$key") {
                Write-EnhancedLog -Message "Validated registry key: $key" -Level 'INFO'
                Write-EnhancedLog "Validated registry key: $key" -Level 'INFO'
            }
            else {
                Write-EnhancedLog -Message "Failed to validate registry key: $key" -Level 'ERROR'
                Write-EnhancedLog "Failed to validate registry key: $key" -Level 'ERROR'
                $importSuccess = $false
            }
        }

        if ($importSuccess) {
            Write-EnhancedLog -Message "Successfully validated all registry keys for: $RegistryFilePath" -Level 'INFO'
        }
        else {
            Write-EnhancedLog -Message "Some registry keys failed to validate for: $RegistryFilePath" -Level 'ERROR'
        }
    }
    catch {
        Handle-Error -ErrorRecord $_
    }
    finally {
        Write-EnhancedLog -Message 'Validate-RegistryKeys function completed' -Level 'INFO'
    }
}
#EndRegion '.\Public\Validate-RegistryKeys.ps1' 38
#Region '.\Public\Validate-RegistryRemoval.ps1' -1

function Validate-RegistryRemoval {
    param (
        [string]$RegistryPath
    )

    Write-EnhancedLog -Message "Starting Validate-RegistryRemoval function for: $RegistryPath" -Level 'INFO'

    try {
        if (Test-Path -Path "Registry::$RegistryPath") {
            Write-EnhancedLog -Message "Registry path still exists: $RegistryPath" -Level 'ERROR'
            Write-Output "Registry path still exists: $RegistryPath"
        } else {
            Write-EnhancedLog -Message "Registry path successfully removed: $RegistryPath" -Level 'INFO'
            Write-Output "Registry path successfully removed: $RegistryPath"
        }
    } catch {
        Handle-Error -ErrorRecord $_
    } finally {
        Write-EnhancedLog -Message 'Validate-RegistryRemoval function completed' -Level 'INFO'
    }
}
#EndRegion '.\Public\Validate-RegistryRemoval.ps1' 22
#Region '.\Public\WaitForRegistryKey.ps1' -1

function WaitForRegistryKey {
    param (
        [string[]]$RegistryPaths,
        [string]$SoftwareName,
        [version]$MinimumVersion,
        [int]$TimeoutSeconds = 120
    )


    Write-EnhancedLog -Message "Starting WaitForRegistryKey function" -Level "INFO"
    Write-EnhancedLog -Message "Checking for $SoftwareName version $MinimumVersion or later" -Level "INFO"

    $elapsedSeconds = 0

    try {
        while ($elapsedSeconds -lt $TimeoutSeconds) {
            foreach ($path in $RegistryPaths) {
                $items = Get-ChildItem -Path $path -ErrorAction SilentlyContinue

                foreach ($item in $items) {
                    $app = Get-ItemProperty -Path $item.PsPath -ErrorAction SilentlyContinue
                    if ($app.DisplayName -like "*$SoftwareName*") {
                        $installedVersion = New-Object Version $app.DisplayVersion
                        if ($installedVersion -ge $MinimumVersion) {
                            Write-EnhancedLog -Message "Found $SoftwareName version $installedVersion at $item.PsPath" -Level "INFO"
                            return @{
                                IsInstalled = $true
                                Version     = $app.DisplayVersion
                                ProductCode = $app.PSChildName
                            }
                        }
                    }
                }
            }

            Start-Sleep -Seconds 1
            $elapsedSeconds++
        }

        Write-EnhancedLog -Message "Timeout reached. $SoftwareName version $MinimumVersion or later not found." -Level "WARNING"
        return @{ IsInstalled = $false }
    }
    catch {
        Handle-Error -ErrorRecord $_
    }
    finally {
        Write-EnhancedLog -Message "WaitForRegistryKey function completed" -Level "INFO"
    }
}
#EndRegion '.\Public\WaitForRegistryKey.ps1' 50