EnhancedDeviceMigrationAO.psm1

#Region '.\Public\Add-LocalUser-Archive.ps1' -1

# # # function Verify-GroupMembers {
# # # [CmdletBinding()]
# # # param (
# # # [Parameter(Mandatory = $true)]
# # # [array]$groupMembers
# # # )

# # # Begin {
# # # Write-EnhancedLog -Message "Starting Verify-GroupMembers function" -Level "Notice"
# # # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# # # }

# # # Process {
# # # Write-EnhancedLog -Message "Verifying remaining members of the group." -Level "INFO"
# # # foreach ($member in $groupMembers) {
# # # try {
# # # # Ensure the SID is valid before processing
# # # if ($member.SID) {
# # # $objSID = New-Object System.Security.Principal.SecurityIdentifier($member.SID)
# # # $objUser = $objSID.Translate([System.Security.Principal.NTAccount])
# # # Write-EnhancedLog -Message "Valid member: $($objUser.Value), SID: $($member.SID)" -Level "INFO"
# # # }
# # # else {
# # # Write-EnhancedLog -Message "Invalid or missing SID for member $($member.Name)" -Level "WARNING"
# # # }
# # # }
# # # catch {
# # # Write-EnhancedLog -Message "Unexpected error resolving member: SID $($member.SID). Error: $($_.Exception.Message)" -Level "ERROR"
# # # }
# # # }
# # # }

# # # End {
# # # Write-EnhancedLog -Message "Exiting Verify-GroupMembers function" -Level "Notice"
# # # }
# # # }




# # function Get-LocalUserAccount {
# # <#
# # .SYNOPSIS
# # Checks if a local user exists and creates the user if necessary.
# # .PARAMETER TempUser
# # The username of the temporary local user.
# # .PARAMETER TempUserPassword
# # The password for the local user.
# # .PARAMETER Description
# # The description for the local user account.
# # #>

# # [CmdletBinding()]
# # param (
# # [Parameter(Mandatory = $true)]
# # [string]$TempUser,

# # [Parameter(Mandatory = $true)]
# # [string]$TempUserPassword,

# # [Parameter(Mandatory = $true)]
# # [string]$Description
# # )

# # Begin {
# # Write-EnhancedLog -Message "Starting Get-LocalUserAccount function for user '$TempUser'" -Level "Notice"
# # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# # }

# # Process {
# # try {
# # Write-EnhancedLog -Message "Checking if local user '$TempUser' exists." -Level "INFO"
# # $userExists = Get-LocalUser -Name $TempUser -ErrorAction SilentlyContinue

# # if (-not $userExists) {
# # Write-EnhancedLog -Message "Creating Local User Account '$TempUser'." -Level "INFO"
# # $Password = ConvertTo-SecureString -AsPlainText $TempUserPassword -Force
# # New-LocalUser -Name $TempUser -Password $Password -Description $Description -AccountNeverExpires
# # Write-EnhancedLog -Message "Local user account '$TempUser' created successfully." -Level "INFO"
# # }
# # else {
# # Write-EnhancedLog -Message "Local user account '$TempUser' already exists." -Level "WARNING"
# # }

# # return $userExists

# # }
# # catch {
# # Write-EnhancedLog -Message "Failed to retrieve or create user '$TempUser': $($_.Exception.Message)" -Level "ERROR"
# # throw
# # }
# # }

# # End {
# # Write-EnhancedLog -Message "Exiting Get-LocalUserAccount function" -Level "Notice"
# # }
# # }

# # # # function Get-LocalGroupWithMembers {
# # # # [CmdletBinding()]
# # # # param (
# # # # [Parameter(Mandatory = $true)]
# # # # [string]$Group
# # # # )

# # # # try {
# # # # Write-EnhancedLog -Message "Retrieving group '$Group' for membership check." -Level "INFO"
# # # # $group = Get-LocalGroup -Name $Group -ErrorAction Stop

# # # # Write-EnhancedLog -Message "Group '$Group' found. Retrieving group members." -Level "INFO"
# # # # $groupMembers = Get-LocalGroupMember -Group $Group -ErrorAction Stop

# # # # Write-EnhancedLog -Message "Group '$Group' has $($groupMembers.Count) members." -Level "INFO"
# # # # return $groupMembers

# # # # }
# # # # catch {
# # # # Write-EnhancedLog -Message "Error retrieving group '$Group' or members: $($_.Exception.Message)" -Level "ERROR"
# # # # throw
# # # # }
# # # # }

# # # function Check-UserGroupMembership {
# # # [CmdletBinding()]
# # # param (
# # # [Parameter(Mandatory = $true)]
# # # [string]$TempUser,

# # # [Parameter(Mandatory = $true)]
# # # [array]$GroupMembers
# # # )

# # # try {
# # # Write-EnhancedLog -Message "Checking if user '$TempUser' is a member of the group." -Level "INFO"
# # # $userSID = (Get-LocalUser -Name $TempUser -ErrorAction Stop).SID
# # # $memberExists = $false

# # # foreach ($member in $GroupMembers) {
# # # Write-EnhancedLog -Message "Checking group member '$($member.Name)' (SID: $($member.SID))." -Level "DEBUG"
# # # if ($member.SID -eq $userSID) {
# # # Write-EnhancedLog -Message "User '$TempUser' is already a member of the group." -Level "INFO"
# # # $memberExists = $true
# # # break
# # # }
# # # }

# # # return $memberExists

# # # }
# # # catch {
# # # Write-EnhancedLog -Message "Error checking user group membership: $($_.Exception.Message)" -Level "ERROR"
# # # throw
# # # }
# # # }

# # # function Add-UserToLocalGroup {
# # # [CmdletBinding()]
# # # param (
# # # [Parameter(Mandatory = $true)]
# # # [string]$TempUser,
# # # [Parameter(Mandatory = $true)]
# # # [string]$Group
# # # )

# # # try {
# # # Write-EnhancedLog -Message "Checking if user '$TempUser' is already a member of group '$Group'." -Level "INFO"
# # # # $groupMembers = Get-LocalGroupMember -Group $Group -ErrorAction Stop
# # # $groupMembers = Get-GroupMembers -GroupName $Group -ErrorAction Stop

# # # # Check if the user is already a member of the group
# # # if ($groupMembers.Name -contains $TempUser) {
# # # Write-EnhancedLog -Message "User '$TempUser' is already a member of the group '$Group'." -Level "INFO"
# # # }
# # # else {
# # # Write-EnhancedLog -Message "Adding user '$TempUser' to group '$Group'." -Level "INFO"
# # # Add-LocalGroupMember -Group $Group -Member $TempUser -ErrorAction Stop
# # # Write-EnhancedLog -Message "User '$TempUser' added to group '$Group'." -Level "INFO"
# # # }
# # # }
# # # catch {
# # # Write-EnhancedLog -Message "Failed to add user '$TempUser' to group '$Group': $($_.Exception.Message)" -Level "ERROR"
# # # throw
# # # }
# # # }





# # # function Add-LocalUser {
# # # [CmdletBinding()]
# # # param (
# # # [Parameter(Mandatory = $true)]
# # # [string]$TempUser,

# # # [Parameter(Mandatory = $true)]
# # # [string]$TempUserPassword,

# # # [Parameter(Mandatory = $true)]
# # # [string]$Description,

# # # [Parameter(Mandatory = $true)]
# # # [string]$Group = "Administrators"
# # # )

# # # Begin {
# # # Write-EnhancedLog -Message "Starting Add-LocalUser function" -Level "Notice"
# # # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# # # }

# # # Process {
# # # try {
# # # # Step 1: Check or create local user
# # # $userExists = Get-LocalUserAccount -TempUser $TempUser -TempUserPassword $TempUserPassword -Description $Description

# # # # Step 2: Retrieve group members using Get-GroupMembers function instead of Get-LocalGroupMember
# # # $groupMembers = Get-GroupMembers -GroupName $Group

# # # if ($groupMembers) {
# # # # Step 3: Check if user is already a member of the group
# # # $isMember = $false
# # # foreach ($member in $groupMembers) {
# # # $accountName = Extract-AccountName -PartComponent $member.PartComponent
# # # if ($accountName -eq $TempUser) {
# # # $isMember = $true
# # # break
# # # }
# # # }

# # # # Step 4: Add user to group if not already a member
# # # if (-not $isMember) {
# # # Add-UserToLocalGroup -TempUser $TempUser -Group $Group
# # # }
# # # else {
# # # Write-EnhancedLog -Message "User '$TempUser' is already a member of the group '$Group'." -Level "WARNING"
# # # }

# # # # Step 5: Verify the group members after modification
# # # Verify-GroupMembers -groupMembers $groupMembers
# # # }
# # # else {
# # # Write-EnhancedLog -Message "No members found in group '$Group'." -Level "WARNING"
# # # }
# # # }
# # # catch {
# # # Write-EnhancedLog -Message "An error occurred in Add-LocalUser: $($_.Exception.Message)" -Level "ERROR"
# # # throw
# # # }
# # # }

# # # End {
# # # Write-EnhancedLog -Message "Exiting Add-LocalUser function" -Level "Notice"
# # # }
# # # }



# # # # function Add-LocalUser {
# # # # <#
# # # # .SYNOPSIS
# # # # Adds a local user and ensures they are part of the correct group.
# # # # .PARAMETER TempUser
# # # # The username of the temporary local user.
# # # # .PARAMETER TempUserPassword
# # # # The password for the local user.
# # # # .PARAMETER Description
# # # # The description for the local user account.
# # # # .PARAMETER Group
# # # # The group to add the local user to.
# # # # #>

# # # # [CmdletBinding()]
# # # # param (
# # # # [Parameter(Mandatory = $true)]
# # # # [string]$TempUser,

# # # # [Parameter(Mandatory = $true)]
# # # # [string]$TempUserPassword,

# # # # [Parameter(Mandatory = $true)]
# # # # [string]$Description,

# # # # [Parameter(Mandatory = $true)]
# # # # [string]$Group
# # # # )

# # # # Begin {
# # # # Write-EnhancedLog -Message "Starting Add-LocalUser function" -Level "Notice"
# # # # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

# # # # # Main script to clean orphaned SIDs and verify group members
      
# # # # # Step 1: Remove orphaned SIDs
# # # # # Example usage to remove orphaned SIDs from the "Administrators" group
# # # # # $params = @{
# # # # # Group = "Administrators"
# # # # # }
# # # # # Remove-OrphanedSIDs @params


# # # # # Remove-OrphanedSIDsFromAdministratorsGroup


# # # # # $GetGroupMembers = @{
# # # # # GroupName = "Administrators"
# # # # # }
# # # # # $groupMembers = Get-GroupMembers @GetGroupMembers


# # # # # $groupMembers = Get-OrphanedSIDs
# # # # $groupMembers = Get-AllGroupAccounts -GroupName "Administrators"
# # # # # $accounts | Format-Table -AutoSize



# # # # # Wait-Debugger

# # # # # Step 2: Retrieve group members
# # # # # $groupMembers = Get-LocalGroupWithMembers -Group $Group

# # # # if ($groupMembers) {
# # # # # Step 3: Verify the remaining group members
# # # # Verify-GroupMembers -groupMembers $groupMembers
# # # # }
        
# # # # Wait-Debugger

# # # # }

# # # # Process {
# # # # try {
# # # # # Step 1: Check or create local user
# # # # $userExists = Get-LocalUserAccount -TempUser $TempUser -TempUserPassword $TempUserPassword -Description $Description


# # # # $GetGroupMembers = @{
# # # # GroupName = "Administrators"
# # # # }
# # # # $groupMembers = Get-GroupMembers @GetGroupMembers

# # # # # Step 2: Retrieve group and its members
# # # # # $groupMembers = Get-LocalGroupWithMembers -Group $Group

# # # # # Step 3: Check if user is a member of the group
# # # # $isMember = Check-UserGroupMembership -TempUser $TempUser -GroupMembers $groupMembers

# # # # # Step 4: Add user to group if not a member
# # # # if (-not $isMember) {
# # # # Add-UserToLocalGroup -TempUser $TempUser -Group $Group
# # # # }

# # # # # Step 5: Remove orphaned SIDs and verify group members
# # # # # Example usage to remove orphaned SIDs from the "Administrators" group
# # # # # $params = @{
# # # # # Group = "Administrators"
# # # # # }
# # # # # Remove-OrphanedSIDs @params

# # # # # Remove-OrphanedSIDsFromAdministratorsGroup


# # # # $GetGroupMembers = @{
# # # # GroupName = "Administrators"
# # # # }
# # # # $groupMembers = Get-GroupMembers @GetGroupMembers
    

# # # # # Retrieve group members
# # # # # $groupMembers = Get-LocalGroupWithMembers -Group $Group

# # # # if ($groupMembers) {
# # # # # Verify the remaining group members
# # # # Verify-GroupMembers -groupMembers $groupMembers
# # # # }
# # # # }
# # # # catch {
# # # # Write-EnhancedLog -Message "An error occurred in Add-LocalUser: $($_.Exception.Message)" -Level "ERROR"
# # # # throw
# # # # }
# # # # }

# # # # End {
# # # # Write-EnhancedLog -Message "Exiting Add-LocalUser function" -Level "Notice"
# # # # }
# # # # }


























# # function Get-GroupMembers {
# # param (
# # [string]$GroupName = "Administrators"
# # )

# # Begin {
# # Write-EnhancedLog -Message "Retrieving members of the '$GroupName' group." -Level "INFO"
# # }

# # Process {
# # try {
# # Write-EnhancedLog -Message "Attempting to retrieve members of the '$GroupName' group." -Level "INFO"
# # $groupPattern = [regex]::Escape($GroupName)
# # $admins = Get-WmiObject -Class Win32_GroupUser | Where-Object { $_.GroupComponent -match $groupPattern }

# # $count = $admins.Count
# # Write-EnhancedLog -Message "Found $count members in the '$GroupName' group." -Level "INFO"

# # if ($count -eq 0) {
# # Write-EnhancedLog -Message "No members found in the '$GroupName' group." -Level "WARNING"
# # }

# # return $admins
# # }
# # catch {
# # Write-EnhancedLog -Message "Failed to retrieve members of the '$GroupName' group: $($_.Exception.Message)" -Level "ERROR"
# # Handle-Error -ErrorRecord $_
# # throw
# # }
# # }
# # }





# # function Verify-GroupMembers {
# # [CmdletBinding()]
# # param (
# # [Parameter(Mandatory = $true)]
# # [array]$groupMembers
# # )

# # Begin {
# # Write-EnhancedLog -Message "Starting Verify-GroupMembers function" -Level "Notice"
# # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# # }

# # Process {
# # Write-EnhancedLog -Message "Verifying remaining members of the group." -Level "INFO"
        
# # foreach ($member in $groupMembers) {
# # try {
# # # Attempt to extract the SID from the PartComponent property
# # $partComponent = $member.PartComponent
# # if ($partComponent -match 'Win32_UserAccount.Domain="[^"]+",Name="([^"]+)"') {
# # $accountName = $matches[1]

# # Write-EnhancedLog -Message "Attempting to resolve account: $accountName" -Level "INFO"
                    
# # # Create SID from the account name
# # $objSID = New-Object System.Security.Principal.SecurityIdentifier($accountName)
# # $objUser = $objSID.Translate([System.Security.Principal.NTAccount])
                    
# # Write-EnhancedLog -Message "Valid member: $($objUser.Value), SID: $($member.PartComponent)" -Level "INFO"
# # } else {
# # Write-EnhancedLog -Message "Invalid or missing SID for member: $($member.PartComponent)" -Level "WARNING"
# # }
# # }
# # catch {
# # Write-EnhancedLog -Message "Unable to resolve member: $($member.PartComponent). Error: $($_.Exception.Message)" -Level "ERROR"
# # }
# # }
# # }

# # End {
# # Write-EnhancedLog -Message "Exiting Verify-GroupMembers function" -Level "Notice"
# # }
# # }









# # function Add-LocalUser {
# # [CmdletBinding()]
# # param (
# # [Parameter(Mandatory = $true)]
# # [string]$TempUser,

# # [Parameter(Mandatory = $true)]
# # [string]$TempUserPassword,

# # [Parameter(Mandatory = $true)]
# # [string]$Description,

# # [Parameter(Mandatory = $true)]
# # [string]$Group = "Administrators"
# # )

# # Begin {
# # Write-EnhancedLog -Message "Starting Add-LocalUser function" -Level "Notice"
# # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# # }

# # Process {
# # try {
# # # Step 1: Check or create local user
# # $userExists = Get-LocalUserAccount -TempUser $TempUser -TempUserPassword $TempUserPassword -Description $Description

# # # Step 2: Retrieve group members using Get-GroupMembers
# # $groupMembers = Get-GroupMembers -GroupName $Group

# # if ($groupMembers) {
# # # Step 3: Check if user is already a member of the group
# # $isMember = $false
# # foreach ($member in $groupMembers) {
# # $accountName = Extract-AccountName -PartComponent $member.PartComponent
# # if ($accountName -eq $TempUser) {
# # $isMember = $true
# # break
# # }
# # }

# # # Step 4: Add user to group if not already a member
# # if (-not $isMember) {
# # Add-UserToLocalGroup -TempUser $TempUser -Group $Group
# # }
# # else {
# # Write-EnhancedLog -Message "User '$TempUser' is already a member of the group '$Group'." -Level "WARNING"
# # }

# # # Step 5: Verify the group members after modification
# # Verify-GroupMembers -groupMembers $groupMembers
# # }
# # else {
# # Write-EnhancedLog -Message "No members found in group '$Group'." -Level "WARNING"
# # }
# # }
# # catch {
# # Write-EnhancedLog -Message "An error occurred in Add-LocalUser: $($_.Exception.Message)" -Level "ERROR"
# # throw
# # }
# # }

# # End {
# # Write-EnhancedLog -Message "Exiting Add-LocalUser function" -Level "Notice"
# # }
# # }







# function Get-GroupMembers {
# param (
# [string]$GroupName = 'Administrators'
# )

# # Retrieve all group members via WMI
# try {
# $group = Get-WmiObject -Class Win32_Group -Filter "Name='$GroupName'"
# $members = $group.GetRelated("Win32_UserAccount")
# if (!$members) {
# Write-EnhancedLog -Message "No members found in group $GroupName." -Level "WARNING"
# }
# return $members
# }
# catch {
# Write-EnhancedLog -Message "Error retrieving members from group $GroupName $_" -Level "ERROR"
# return $null
# }
# }

# function Resolve-SID {
# param (
# [string]$AccountName
# )

# try {
# $account = Get-WmiObject -Class Win32_UserAccount -Filter "Name='$AccountName'"
# if ($account) {
# $sid = New-Object System.Security.Principal.SecurityIdentifier($account.SID)
# return $sid
# } else {
# Write-EnhancedLog -Message "Unable to resolve SID for $AccountName." -Level "WARNING"
# return $null
# }
# }
# catch {
# Write-EnhancedLog -Message "Error resolving SID for $AccountName $_" -Level "ERROR"
# return $null
# }
# }





# # # Main Logic
# # Write-EnhancedLog -Message "Starting group member verification..." -Level "NOTICE"

# # # Verify current group members
# # Verify-GroupMembers -GroupName 'Administrators'

# # # Ensure TempUser is in the Administrators group
# # Add-UserToGroup -UserName 'TempUser' -GroupName 'Administrators'

# # Write-EnhancedLog -Message "Group member verification completed." -Level "NOTICE"








#EndRegion '.\Public\Add-LocalUser-Archive.ps1' 635
#Region '.\Public\Add-UserToGroup-Archive.ps1' -1




# function Add-UserToGroup {
# param (
# [string]$UserName = 'TempUser',
# [string]$GroupName = 'Administrators'
# )

# $group = [ADSI]"WinNT://./$GroupName,group"
# $user = [ADSI]"WinNT://./$UserName,user"

# # Check if user exists
# try {
# if (-not $user.PSBase.Name) {
# Write-EnhancedLog -Message "User $UserName does not exist. Cannot add to group." -Level "ERROR"
# return
# }
# }
# catch {
# Write-EnhancedLog -Message "User $UserName does not exist. Cannot add to group." -Level "ERROR"
# return
# }

# try {
# if (-not $group.PSBase.Invoke("IsMember", $user.PSBase.Path)) {
# Write-EnhancedLog -Message "Adding $UserName to $GroupName group..." -Level "INFO"
# $group.PSBase.Invoke("Add", $user.PSBase.Path)
# Write-EnhancedLog -Message "$UserName has been successfully added to $GroupName group." -Level "INFO"
# } else {
# Write-EnhancedLog -Message "$UserName is already a member of the $GroupName group." -Level "INFO"
# }
# }
# catch {
# Write-EnhancedLog -Message "Error adding $UserName to $GroupName group: $_" -Level "ERROR"
# }
# }

# # # Main Logic
# # Write-EnhancedLog -Message "Starting group member verification..." -Level "NOTICE"

# # # Verify current group members
# # Verify-GroupMembers -GroupName 'Administrators'

# # # Ensure TempUser is in the Administrators group
# # Add-UserToGroup -UserName 'TempUser' -GroupName 'Administrators'

# # Write-EnhancedLog -Message "Group member verification completed." -Level "NOTICE"
#EndRegion '.\Public\Add-UserToGroup-Archive.ps1' 49
#Region '.\Public\Analyze-CopyOperationStatus.ps1' -1

function Analyze-CopyOperationStatus {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the log folder path.")]
        [string]$LogFolder,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the status file name.")]
        [string]$StatusFileName,

        [Parameter(Mandatory = $true)]
        [int]$MaxRetries = 5,

        [Parameter(Mandatory = $true)]
        [int]$RetryInterval = 10
    )

    Begin {
        Write-EnhancedLog -Message "Starting Analyze-CopyOperationStatus function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # # Step 1: Remove existing status files
            # Remove-ExistingStatusFiles -LogFolder $LogFolder -StatusFileName $StatusFileName

            # Step 2: Find the new status file
            $statusFile = Find-NewStatusFile -LogFolder $LogFolder -StatusFileName $StatusFileName -MaxRetries $MaxRetries -RetryInterval $RetryInterval

            # Step 3: Analyze the status file
            Write-EnhancedLog -Message "Reading status file: $($statusFile.FullName)" -Level "INFO"
            $statusData = Get-Content -Path $statusFile.FullName | ConvertFrom-Json

            # Analyze the status of each operation
            Write-EnhancedLog -Message "Analyzing copy operation status from the JSON data" -Level "INFO"
            foreach ($entry in $statusData) {
                $sourcePath = $entry.SourcePath
                $backupFolderName = $entry.BackupFolderName
                $backupStatus = $entry.BackupStatus
                $timestamp = $entry.Timestamp

                if ($backupStatus -eq "Success") {
                    Write-EnhancedLog -Message "Copy operation succeeded: Source: $sourcePath, Backup Folder: $backupFolderName, Timestamp: $timestamp" -Level "INFO"
                }
                elseif ($backupStatus -eq "Failed") {
                    Write-EnhancedLog -Message "Copy operation failed: Source: $sourcePath, Backup Folder: $backupFolderName, Timestamp: $timestamp" -Level "ERROR"
                    if ($entry.VerificationResults) {
                        foreach ($result in $entry.VerificationResults) {
                            Write-EnhancedLog -Message "Discrepancy: Status: $($result.Status), Source Path: $($result.SourcePath), Expected/Actual Path: $($result.ExpectedPath -or $result.ActualPath)" -Level "WARNING"
                        }
                    }
                }
                else {
                    Write-EnhancedLog -Message "Unknown copy operation status for Source: $sourcePath, Backup Folder: $backupFolderName, Timestamp: $timestamp" -Level "WARNING"
                }
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Analyze-CopyOperationStatus function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Analyze-CopyOperationStatus function" -Level "Notice"
    }
}
#EndRegion '.\Public\Analyze-CopyOperationStatus.ps1' 69
#Region '.\Public\Analyze-OneDriveSyncUtilStatus.ps1' -1

function Analyze-OneDriveSyncUtilStatus {
    <#
    .SYNOPSIS
    Analyzes the OneDrive sync status from a JSON file.
 
    .DESCRIPTION
    The Analyze-OneDriveSyncUtilStatus function removes existing status files, finds the new status file, reads it, and categorizes the sync status as healthy, in progress, or failed based on predefined conditions.
 
    .PARAMETER LogFolder
    The path to the folder where the log files are stored. If running as SYSTEM, this will be ignored, and the function will analyze logs across all user profiles.
 
    .PARAMETER StatusFileName
    The name of the JSON file containing the OneDrive sync status.
 
    .PARAMETER MaxRetries
    The maximum number of retries to find the new status file.
 
    .PARAMETER RetryInterval
    The interval in seconds between retries.
 
    .EXAMPLE
    $result = Analyze-OneDriveSyncUtilStatus -LogFolder "C:\Users\YourUserProfile\Logs" -StatusFileName "ODSyncUtilStatus.json" -MaxRetries 5 -RetryInterval 10
    if ($result.Status -eq "Healthy") {
        # Do something if healthy
    }
    # Analyzes the OneDrive sync status from the specified JSON file and returns an object.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$LogFolder,

        [Parameter(Mandatory = $true)]
        [string]$StatusFileName,

        [Parameter(Mandatory = $true)]
        [int]$MaxRetries = 5,

        [Parameter(Mandatory = $true)]
        [int]$RetryInterval = 10
    )

    Begin {
        Write-EnhancedLog -Message "Starting Analyze-OneDriveSyncUtilStatus function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # # Step 1: Remove existing status files
            # Remove-ExistingStatusFiles -LogFolder $LogFolder -StatusFileName $StatusFileName

            # Step 2: Find the new status file
            $statusFile = Find-NewStatusFile -LogFolder $LogFolder -StatusFileName $StatusFileName -MaxRetries $MaxRetries -RetryInterval $RetryInterval

            # Step 3: Analyze the new status file
            Write-EnhancedLog -Message "Reading status file: $($statusFile.FullName)" -Level "INFO"
            $Status = Get-Content -Path $statusFile.FullName | ConvertFrom-Json

            # Define the status categories
            Write-EnhancedLog -Message "Defining status categories for analysis" -Level "INFO"
            $Success = @("Synced", "UpToDate", "Up To Date")
            $InProgress = @("Syncing", "SharedSync", "Shared Sync")
            $Failed = @("Error", "ReadOnly", "Read Only", "OnDemandOrUnknown", "On Demand or Unknown", "Paused")

            # Analyze the status and return an object
            Write-EnhancedLog -Message "Analyzing status from the JSON data" -Level "INFO"
            $StatusString = $Status.CurrentStateString
            $UserName = $Status.UserName
            $result = [PSCustomObject]@{
                UserName = $UserName
                Status   = $null
                Message  = $null
            }

            if ($StatusString -in $Success) {
                $result.Status = "Healthy"
                $result.Message = "OneDrive sync status is healthy"
                Write-EnhancedLog -Message "$($result.Message): User: $UserName, Status: $StatusString" -Level "INFO"
            }
            elseif ($StatusString -in $InProgress) {
                $result.Status = "InProgress"
                $result.Message = "OneDrive sync status is currently syncing"
                Write-EnhancedLog -Message "$($result.Message): User: $UserName, Status: $StatusString" -Level "WARNING"
            }
            elseif ($StatusString -in $Failed) {
                $result.Status = "Failed"
                $result.Message = "OneDrive sync status is in a known error state"
                Write-EnhancedLog -Message "$($result.Message): User: $UserName, Status: $StatusString" -Level "ERROR"
            }
            else {
                $result.Status = "Unknown"
                $result.Message = "Unable to determine OneDrive Sync Status"
                Write-EnhancedLog -Message "$($result.Message) for User: $UserName" -Level "WARNING"
            }

            return $result
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Analyze-OneDriveSyncUtilStatus function: $($_.Exception.Message)" -Level "ERROR"
            Write-EnhancedLog -Message "Please check if you are logged in to OneDrive and try again." -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Analyze-OneDriveSyncUtilStatus function" -Level "Notice"
    }
}



# # Step 1: Remove existing status files
# Remove-ExistingStatusFiles -LogFolder "C:\Users\YourUserProfile\Logs" -StatusFileName "ODSyncUtilStatus.json"

# # Step 2: Find the new status file
# $statusFile = Find-NewStatusFile -LogFolder "C:\Users\YourUserProfile\Logs" -StatusFileName "ODSyncUtilStatus.json" -MaxRetries 5 -RetryInterval 10

# # Step 3: Analyze the new status file
# $result = Analyze-OneDriveSyncUtilStatus -StatusFile $statusFile

# # Check the result
# if ($result.Status -eq "Healthy") {
# # Do something if healthy
# }
#EndRegion '.\Public\Analyze-OneDriveSyncUtilStatus.ps1' 128
#Region '.\Public\Apply-RegistrySettings.ps1' -1

# function Apply-RegistrySettings {
# <#
# .SYNOPSIS
# Applies multiple registry settings and provides a summary of success, failures, and warnings.

# .DESCRIPTION
# This function applies registry settings from a provided hash table and logs the results, including a final summary of success, skipped, and failed settings.

# .PARAMETER RegistrySettings
# A hash table containing the registry settings to apply. The hash table keys should be registry paths, and the values should be another hash table with registry names, types, and data.

# .EXAMPLE
# Apply-RegistrySettings -RegistrySettings $RegistrySettings
# #>

# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true)]
# $RegistrySettings
# )

# Begin {
# # Initialize counters and summary table
# $infoCount = 0
# $warningCount = 0
# $errorCount = 0
# $summaryTable = [System.Collections.Generic.List[PSCustomObject]]::new()

# Write-EnhancedLog -Message "Applying registry settings" -Level "INFO"
# }

# Process {
# foreach ($regPath in $RegistrySettings.Keys) {
# foreach ($regName in $RegistrySettings[$regPath].Keys) {
# $regSetting = $RegistrySettings[$regPath][$regName]

# $summaryRow = [PSCustomObject]@{
# RegistryPath = $regPath
# RegistryName = $regName
# RegistryValue = if ($null -ne $regSetting["Data"]) { $regSetting["Data"] } else { "null" }
# Status = ""
# }

# if ($null -ne $regSetting["Data"]) {
# Write-EnhancedLog -Message "Setting registry value $regName at $regPath" -Level "INFO"
# $infoCount++

# $regParams = @{
# RegKeyPath = $regPath
# RegValName = $regName
# RegValType = $regSetting["Type"]
# RegValData = $regSetting["Data"]
# }

# # If the data is an empty string, explicitly set it as such
# if ($regSetting["Data"] -eq "") {
# $regParams.RegValData = ""
# }

# try {
# # Call the Set-RegistryValue function and capture the result
# $setRegistryResult = Set-RegistryValue @regParams

# # Build decision-making logic based on the result
# if ($setRegistryResult -eq $true) {
# Write-EnhancedLog -Message "Successfully set the registry value: $($regParams.RegValName) at $($regParams.RegKeyPath)" -Level "INFO"
# $summaryRow.Status = "Success"
# }
# else {
# Write-EnhancedLog -Message "Failed to set the registry value: $($regParams.RegValName) at $($regParams.RegKeyPath)" -Level "ERROR"
# $summaryRow.Status = "Failed"
# $errorCount++
# }

# Write-EnhancedLog -Message "Registry value $regName at $regPath set" -Level "INFO"
                        
# }
# catch {
# Write-EnhancedLog -Message "Error setting registry value $regName at $regPath $($_.Exception.Message)" -Level "ERROR"
# $errorCount++
# $summaryRow.Status = "Failed"
# }
# }
# else {
# Write-EnhancedLog -Message "Skipping registry value $regName at $regPath due to null data" -Level "WARNING"
# $warningCount++
# $summaryRow.Status = "Skipped"
# }

# $summaryTable.Add($summaryRow)
# }
# }
# }

# End {
# # Final Summary Report
# Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"
# Write-EnhancedLog -Message "Final Summary Report" -Level "NOTICE"
# Write-EnhancedLog -Message "Total registry settings processed: $($infoCount + $warningCount + $errorCount)" -Level "INFO"
# Write-EnhancedLog -Message "Successfully applied registry settings: $infoCount" -Level "INFO"
# Write-EnhancedLog -Message "Skipped registry settings (due to null data): $warningCount" -Level "WARNING"
# Write-EnhancedLog -Message "Failed registry settings: $errorCount" -Level "ERROR"
# Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"

# # Color-coded summary for the console
# Write-Host "----------------------------------------" -ForegroundColor White
# Write-Host "Final Summary Report" -ForegroundColor Cyan
# Write-Host "Total registry settings processed: $($infoCount + $warningCount + $errorCount)" -ForegroundColor White
# Write-Host "Successfully applied registry settings: $infoCount" -ForegroundColor Green
# Write-Host "Skipped registry settings (due to null data): $warningCount" -ForegroundColor Yellow
# Write-Host "Failed registry settings: $errorCount" -ForegroundColor Red
# Write-Host "----------------------------------------" -ForegroundColor White

# # Display the summary table of registry keys and their final states
# Write-Host "Registry Settings Summary:" -ForegroundColor Cyan
# $summaryTable | Format-Table -AutoSize

# # Optionally log the summary to the enhanced log as well
# foreach ($row in $summaryTable) {
# Write-EnhancedLog -Message "RegistryPath: $($row.RegistryPath), RegistryName: $($row.RegistryName), Value: $($row.RegistryValue), Status: $($row.Status)" -Level "INFO"
# }
# }
# }












# function Apply-RegistrySettings {
# param (
# [Parameter(Mandatory = $true)]
# [hashtable[]]$RegistrySettings,
# [string]$RegKeyPath
# )

# Begin {
# $infoCount = 0
# $warningCount = 0
# $errorCount = 0
# $summaryTable = [System.Collections.Generic.List[PSCustomObject]]::new()

# Write-EnhancedLog -Message "Applying registry settings" -Level "INFO"
# }

# Process {
# foreach ($regSetting in $RegistrySettings) {
# $summaryRow = [PSCustomObject]@{
# RegistryPath = $RegKeyPath
# RegistryName = $regSetting.RegValName
# RegistryValue = if ($null -ne $regSetting.RegValData) { $regSetting.RegValData } else { "null" }
# Status = ""
# }

# if ($null -ne $regSetting.RegValData) {
# Write-EnhancedLog -Message "Setting registry value $($regSetting.RegValName) at $RegKeyPath" -Level "INFO"
# $infoCount++

# $regParams = @{
# RegKeyPath = $RegKeyPath
# RegValName = $regSetting.RegValName
# RegValType = $regSetting.RegValType
# RegValData = $regSetting.RegValData
# }

# if ($regSetting.RegValData -eq "") {
# $regParams.RegValData = ""
# }

# try {
# $setRegistryResult = Set-RegistryValue @regParams

# if ($setRegistryResult -eq $true) {
# Write-EnhancedLog -Message "Successfully set the registry value: $($regParams.RegValName) at $($regParams.RegKeyPath)" -Level "INFO"
# $summaryRow.Status = "Success"
# }
# else {
# Write-EnhancedLog -Message "Failed to set the registry value: $($regParams.RegValName) at $($regParams.RegKeyPath)" -Level "ERROR"
# $summaryRow.Status = "Failed"
# $errorCount++
# }
# }
# catch {
# Write-EnhancedLog -Message "Error setting registry value $($regSetting.RegValName) at $RegKeyPath $($_.Exception.Message)" -Level "ERROR"
# $errorCount++
# $summaryRow.Status = "Failed"
# }
# }
# else {
# Write-EnhancedLog -Message "Skipping registry value $($regSetting.RegValName) at $RegKeyPath due to null data" -Level "WARNING"
# $warningCount++
# $summaryRow.Status = "Skipped"
# }

# $summaryTable.Add($summaryRow)
# }
# }
# End {
# # Final Summary Report
# Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"
# Write-EnhancedLog -Message "Final Summary Report" -Level "NOTICE"
# Write-EnhancedLog -Message "Total registry settings processed: $($infoCount + $warningCount + $errorCount)" -Level "INFO"
# Write-EnhancedLog -Message "Successfully applied registry settings: $infoCount" -Level "INFO"
# Write-EnhancedLog -Message "Skipped registry settings (due to null data): $warningCount" -Level "WARNING"
# Write-EnhancedLog -Message "Failed registry settings: $errorCount" -Level "ERROR"
# Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"

# # Color-coded summary for the console
# Write-Host "----------------------------------------" -ForegroundColor White
# Write-Host "Final Summary Report" -ForegroundColor Cyan
# Write-Host "Total registry settings processed: $($infoCount + $warningCount + $errorCount)" -ForegroundColor White
# Write-Host "Successfully applied registry settings: $infoCount" -ForegroundColor Green
# Write-Host "Skipped registry settings (due to null data): $warningCount" -ForegroundColor Yellow
# Write-Host "Failed registry settings: $errorCount" -ForegroundColor Red
# Write-Host "----------------------------------------" -ForegroundColor White

# # Display the summary table of registry keys and their final states
# Write-Host "Registry Settings Summary:" -ForegroundColor Cyan
# $summaryTable | Format-Table -AutoSize

# # Optionally log the summary to the enhanced log as well
# foreach ($row in $summaryTable) {
# Write-EnhancedLog -Message "RegistryPath: $($row.RegistryPath), RegistryName: $($row.RegistryName), Value: $($row.RegistryValue), Status: $($row.Status)" -Level "INFO"
# }
# }
# }











function Apply-RegistrySettings {
    param (
        [Parameter(Mandatory = $true)]
        [hashtable[]]$RegistrySettings,
        [string]$RegKeyPath  # Ensure this is passed correctly
    )

    Begin {
        # Initialize counters and summary table
        $infoCount = 0
        $warningCount = 0
        $errorCount = 0
        $summaryTable = [System.Collections.Generic.List[PSCustomObject]]::new()

        Write-EnhancedLog -Message "Applying registry settings" -Level "INFO"
    }

    Process {
        if (-not $RegKeyPath) {
            Write-EnhancedLog -Message "Error: RegKeyPath is not defined." -Level "ERROR"
            throw "RegKeyPath must be defined for Apply-RegistrySettings to work."
        }

        foreach ($regSetting in $RegistrySettings) {
            $summaryRow = [PSCustomObject]@{
                RegistryPath  = $RegKeyPath
                RegistryName  = $regSetting.RegValName
                RegistryValue = if ($null -ne $regSetting.RegValData) { $regSetting.RegValData } else { "null" }
                Status        = ""
            }

            if ($null -ne $regSetting.RegValData) {
                Write-EnhancedLog -Message "Setting registry value $($regSetting.RegValName) at $RegKeyPath" -Level "INFO"
                $infoCount++

                $regParams = @{
                    RegKeyPath = $RegKeyPath
                    RegValName = $regSetting.RegValName
                    RegValType = $regSetting.RegValType
                    RegValData = $regSetting.RegValData
                }

                if ($regSetting.RegValData -eq "") {
                    $regParams.RegValData = ""
                }

                try {
                    $setRegistryResult = Set-RegistryValue @regParams

                    if ($setRegistryResult -eq $true) {
                        Write-EnhancedLog -Message "Successfully set the registry value: $($regParams.RegValName) at $($regParams.RegKeyPath)" -Level "INFO"
                        $summaryRow.Status = "Success"
                    }
                    else {
                        Write-EnhancedLog -Message "Failed to set the registry value: $($regParams.RegValName) at $($regParams.RegKeyPath)" -Level "ERROR"
                        $summaryRow.Status = "Failed"
                        $errorCount++
                    }
                }
                catch {
                    Write-EnhancedLog -Message "Error setting registry value $($regSetting.RegValName) at $RegKeyPath $($_.Exception.Message)" -Level "ERROR"
                    $errorCount++
                    $summaryRow.Status = "Failed"
                }
            }
            else {
                Write-EnhancedLog -Message "Skipping registry value $($regSetting.RegValName) at $RegKeyPath due to null data" -Level "WARNING"
                $warningCount++
                $summaryRow.Status = "Skipped"
            }

            $summaryTable.Add($summaryRow)
        }
    }
    End {
        # Final Summary Report
        Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"
        Write-EnhancedLog -Message "Final Summary Report" -Level "NOTICE"
        Write-EnhancedLog -Message "Total registry settings processed: $($infoCount + $warningCount + $errorCount)" -Level "INFO"
        Write-EnhancedLog -Message "Successfully applied registry settings: $infoCount" -Level "INFO"
        Write-EnhancedLog -Message "Skipped registry settings (due to null data): $warningCount" -Level "WARNING"
        Write-EnhancedLog -Message "Failed registry settings: $errorCount" -Level "ERROR"
        Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"

        # Color-coded summary for the console
        Write-Host "----------------------------------------" -ForegroundColor White
        Write-Host "Final Summary Report" -ForegroundColor Cyan
        Write-Host "Total registry settings processed: $($infoCount + $warningCount + $errorCount)" -ForegroundColor White
        Write-Host "Successfully applied registry settings: $infoCount" -ForegroundColor Green
        Write-Host "Skipped registry settings (due to null data): $warningCount" -ForegroundColor Yellow
        Write-Host "Failed registry settings: $errorCount" -ForegroundColor Red
        Write-Host "----------------------------------------" -ForegroundColor White

        # Display the summary table of registry keys and their final states
        Write-Host "Registry Settings Summary:" -ForegroundColor Cyan
        $summaryTable | Format-Table -AutoSize

        # Optionally log the summary to the enhanced log as well
        foreach ($row in $summaryTable) {
            Write-EnhancedLog -Message "RegistryPath: $($row.RegistryPath), RegistryName: $($row.RegistryName), Value: $($row.RegistryValue), Status: $($row.Status)" -Level "INFO"
        }
    }
}

#EndRegion '.\Public\Apply-RegistrySettings.ps1' 348
#Region '.\Public\Backup-UserFilesToOneDrive.ps1' -1

function Backup-UserFilesToOneDrive {
    <#
    .SYNOPSIS
    Backs up user files to a specified OneDrive folder and logs the results.
 
    .DESCRIPTION
    The Backup-UserFilesToOneDrive function copies files from a specified source directory to a OneDrive backup directory.
    It verifies the operation, logs the results, and saves the status to a JSON file. The function handles errors gracefully and appends the backup status to the JSON file.
 
    .PARAMETER SourcePath
    The path to the directory containing the files to be backed up.
 
    .PARAMETER BackupFolderName
    The name of the folder where the backup will be stored in the OneDrive directory.
 
    .PARAMETER Exclude
    A list of files or directories to exclude from the backup operation.
 
    .PARAMETER RetryCount
    The number of times to retry the backup operation if it fails.
 
    .PARAMETER WaitTime
    The time to wait between retry attempts, in seconds.
 
    .PARAMETER RequiredSpaceGB
    The amount of free space required at the destination in gigabytes.
 
    .PARAMETER OneDriveBackupPath
    The path to the OneDrive directory where the backup will be stored.
 
    .PARAMETER Scriptbasepath
    The base path of the script, used to determine where to store logs.
 
    .PARAMETER ClearPreviousStatus
    If set to $true, removes the existing JSON status file before starting the backup. Defaults to $false.
 
    .EXAMPLE
    Backup-UserFilesToOneDrive -SourcePath "$env:USERPROFILE\AppData\Local\Google\Chrome\User Data\Default" `
                               -BackupFolderName "ChromeBackup" `
                               -OneDriveBackupPath "$env:OneDrive\Backups" `
                               -Scriptbasepath "$PSScriptRoot" `
                               -ClearPreviousStatus $true
 
    This command backs up Chrome bookmarks to the OneDrive backup folder and removes the existing JSON status file before starting.
 
    .EXAMPLE
    Backup-UserFilesToOneDrive -SourcePath "$env:USERPROFILE\AppData\Roaming\Microsoft\Signatures" `
                               -BackupFolderName "OutlookSignatures" `
                               -OneDriveBackupPath "$env:OneDrive\Backups" `
                               -Scriptbasepath "$PSScriptRoot"
 
    This command backs up Outlook signatures to the OneDrive backup folder without clearing the existing JSON status file.
 
    .NOTES
    The function handles verification of the copy operation and appends the results to a JSON log file.
 
    .LINK
    https://docs.microsoft.com/en-us/powershell/scripting
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$SourcePath,
        [Parameter(Mandatory = $true)]
        [string]$BackupFolderName,
        [Parameter(Mandatory = $false)]
        [string[]]$Exclude,
        [Parameter(Mandatory = $false)]
        [int]$RetryCount = 2,
        [Parameter(Mandatory = $false)]
        [int]$WaitTime = 5,
        [Parameter(Mandatory = $false)]
        [int]$RequiredSpaceGB = 10,
        [Parameter(Mandatory = $true)]
        [string]$OneDriveBackupPath,
        [Parameter(Mandatory = $true)]
        [string]$Scriptbasepath
        # [Parameter(Mandatory = $false)]
        # [bool]$ClearPreviousStatus = $true
    )

    Begin {
        Write-EnhancedLog -Message "Starting Backup-UserFilesToOneDrive function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Define the log file path
        $logFolder = Join-Path -Path $env:USERPROFILE -ChildPath "logs"
        $statusFile = Join-Path -Path $logFolder -ChildPath "UserFilesBackupStatus.json"


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

        # # Clear the existing JSON status file if specified
        # if ($ClearPreviousStatus -and (Test-Path -Path $statusFile)) {
        # Remove-Item -Path $statusFile -Force
        # Write-EnhancedLog -Message "Previous JSON status file removed: $statusFile" -Level "INFO"
        # }

        # Ensure the backup directory exists
        $backupPath = Join-Path -Path $OneDriveBackupPath -ChildPath $BackupFolderName
        if (-not (Test-Path -Path $backupPath)) {
            New-Item -Path $backupPath -ItemType Directory | Out-Null
        }
    }

    Process {
        try {
            # Perform the backup operation
            $CopyFilesWithRobocopyParams = @{
                Source          = $SourcePath
                Destination     = $backupPath
                Exclude         = $Exclude
                RetryCount      = $RetryCount
                WaitTime        = $WaitTime
                RequiredSpaceGB = $RequiredSpaceGB
            }
            
            Copy-FilesWithRobocopy @CopyFilesWithRobocopyParams

            # Verify the copy operation
            $verificationResults = Verify-CopyOperation -SourcePath $SourcePath -DestinationPath $backupPath

            # $DBG

            # Determine backup status based on verification results
            $backupStatus = if ($verificationResults.Count -eq 0) { "Success" } else { "Failed" }

            # Prepare the status entry
            $status = @{
                SourcePath          = $SourcePath
                BackupFolderName    = $BackupFolderName
                BackupPath          = $backupPath
                BackupStatus        = $backupStatus
                VerificationResults = if ($verificationResults.Count -eq 0) { @() } else { $verificationResults }
                Timestamp           = (Get-Date).ToString("o")
            }

            # Load existing JSON file content if it exists, ensuring it's treated as an array
            $existingStatus = @()
            if (Test-Path -Path $statusFile) {
                $existingStatus = Get-Content -Path $statusFile | ConvertFrom-Json
                if ($existingStatus -isnot [System.Collections.ArrayList] -and $existingStatus -is [PSCustomObject]) {
                    $existingStatus = @($existingStatus)
                }
            }

            # Append the new status entry
            $updatedStatus = $existingStatus + $status

            # Save the updated status to the JSON file
            $updatedStatus | ConvertTo-Json -Depth 5 | Out-File -FilePath $statusFile -Force -Encoding utf8

            Write-EnhancedLog -Message "Backup status has been saved to $statusFile" -Level "INFO"
        }
        catch {
            $status = @{
                SourcePath       = $SourcePath
                BackupFolderName = $BackupFolderName
                BackupPath       = $backupPath
                BackupStatus     = "Failed"
                ErrorMessage     = $_.Exception.Message
                Timestamp        = (Get-Date).ToString("o")
            }

            # Load existing JSON file content if it exists, ensuring it's treated as an array
            $existingStatus = @()
            if (Test-Path -Path $statusFile) {
                $existingStatus = Get-Content -Path $statusFile | ConvertFrom-Json
                if ($existingStatus -isnot [System.Collections.ArrayList] -and $existingStatus -is [PSCustomObject]) {
                    $existingStatus = @($existingStatus)
                }
            }

            # Append the new failure entry
            $updatedStatus = $existingStatus + $status

            # Save the updated status to the JSON file
            $updatedStatus | ConvertTo-Json -Depth 5 | Out-File -FilePath $statusFile -Force -Encoding utf8

            Write-EnhancedLog -Message "An error occurred during backup: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Backup-UserFilesToOneDrive function" -Level "Notice"
    }
}
#EndRegion '.\Public\Backup-UserFilesToOneDrive.ps1' 193
#Region '.\Public\Block-UserInput.ps1' -1

function Block-UserInput {
    <#
    .SYNOPSIS
    Blocks or unblocks user input.
 
    .DESCRIPTION
    The Block-UserInput function blocks or unblocks user input using the user32.dll library. This can be useful during critical operations to prevent user interference.
 
    .PARAMETER Block
    A boolean value indicating whether to block (true) or unblock (false) user input.
 
    .EXAMPLE
    $params = @{
        Block = $true
    }
    Block-UserInput @params
    Blocks user input.
 
    .EXAMPLE
    $params = @{
        Block = $false
    }
    Block-UserInput @params
    Unblocks user input.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [bool]$Block
    )

    Begin {
        Write-EnhancedLog -Message "Starting Block-UserInput function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            $code = @"
    [DllImport("user32.dll")]
    public static extern bool BlockInput(bool fBlockIt);
"@

            $userInput = Add-Type -MemberDefinition $code -Name Blocker -Namespace UserInput -PassThru

            Write-EnhancedLog -Message "Blocking user input: $Block" -Level "INFO"
            $null = $userInput::BlockInput($Block)
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Block-UserInput function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Block-UserInput function" -Level "Notice"
    }
}

# # Example usage
# $params = @{
# Block = $true
# }
# Block-UserInput @params
#EndRegion '.\Public\Block-UserInput.ps1' 66
#Region '.\Public\Check-DomainMembership.ps1' -1

function Check-DomainMembership {
    <#
    .SYNOPSIS
    Checks if the computer is part of a domain.
 
    .DESCRIPTION
    This function checks the current domain membership status of the computer by querying the Win32_ComputerSystem class.
 
    .EXAMPLE
    $isDomainJoined = Check-DomainMembership
 
    This will check if the computer is part of a domain and return a Boolean value.
 
    .NOTES
    This function returns a Boolean indicating whether the computer is part of a domain or not.
    #>


    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Check-DomainMembership function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            Write-EnhancedLog -Message "Querying Win32_ComputerSystem to check domain membership status" -Level "INFO"

            # Query the computer system's domain membership status
            $ComputerSystem = Get-WmiObject -Class Win32_ComputerSystem
            $PartOfDomain = $ComputerSystem.PartOfDomain

            if ($PartOfDomain) {
                Write-EnhancedLog -Message "Device is part of a domain." -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Device is not part of a domain." -Level "WARNING"
            }

            return $PartOfDomain
        }
        catch {
            Write-EnhancedLog -Message "Error occurred while checking domain membership: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            # Log exit and cleanup
            Write-EnhancedLog -Message "Exiting Check-DomainMembership function" -Level "NOTICE"
        }
    }

    End {
        Write-EnhancedLog -Message "Check-DomainMembership function completed" -Level "INFO"
    }
}
#EndRegion '.\Public\Check-DomainMembership.ps1' 58
#Region '.\Public\Check-IntuneEnrollment.ps1' -1

# Function to check Intune enrollment status
function Check-IntuneEnrollment {
    param (
        [string]$OMADMPath,
        [string]$EnrollmentBasePath,
        [string]$MSDMProviderID
    )

    Write-EnhancedLog -Message "Checking Intune enrollment status from registry path $OMADMPath" -Level "INFO"
    $Account = (Get-ItemProperty -Path $OMADMPath -ErrorAction SilentlyContinue).PSChildName
    if ($null -eq $Account) {
        Write-EnhancedLog -Message "No Intune enrollment account found at path $OMADMPath" -Level "WARNING"
        return $null
    }

    Write-EnhancedLog -Message "Enrollment account detected: $Account" -Level "INFO"
    $EnrollmentPath = "$EnrollmentBasePath\$Account"

    Write-EnhancedLog -Message "Checking UPN and ProviderID at enrollment path: $EnrollmentPath" -Level "INFO"
    $EnrollmentUPN = (Get-ItemProperty -Path $EnrollmentPath -ErrorAction SilentlyContinue).UPN
    $ProviderID = (Get-ItemProperty -Path $EnrollmentPath -ErrorAction SilentlyContinue).ProviderID

    if (-not $EnrollmentUPN) {
        Write-EnhancedLog -Message "Enrollment UPN not found at $EnrollmentPath" -Level "WARNING"
        return $null
    }

    if ($ProviderID -ne $MSDMProviderID) {
        Write-EnhancedLog -Message "ProviderID does not match the expected value at $EnrollmentPath" -Level "WARNING"
        return $null
    }

    return $Account
}
#EndRegion '.\Public\Check-IntuneEnrollment.ps1' 35
#Region '.\Public\Check-ODSyncUtilStatus.ps1' -1

function Check-ODSyncUtilStatus {
    <#
    .SYNOPSIS
    Checks the OneDrive sync status using ODSyncUtil and stores it in a JSON file.
 
    .DESCRIPTION
    The Check-ODSyncUtilStatus function calls the `Get-ODStatus.ps1` script located in the specified path, retrieves the OneDrive sync status, and saves it to a JSON file in the designated log directory.
 
    .PARAMETER ScriptPath
    The path to the directory containing the `Get-ODStatus.ps1` script.
 
    .PARAMETER LogFolderName
    The name of the folder where the log files will be stored.
 
    .PARAMETER StatusFileName
    The name of the file where the OneDrive sync status will be saved.
 
    .EXAMPLE
    $params = @{
        ScriptPath = "C:\code\IntuneDeviceMigration\DeviceMigration\Files\ODSyncUtil"
        LogFolderName = "logs"
        StatusFileName = "ODSyncUtilStatus.json"
    }
    Check-ODSyncUtilStatus @params
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ScriptPath,

        [Parameter(Mandatory = $false)]
        [string]$LogFolderName = "logs",

        [Parameter(Mandatory = $false)]
        [string]$StatusFileName = "ODSyncUtilStatus.json"
    )

    Begin {
        Write-EnhancedLog -Message "Starting Check-ODSyncUtilStatus function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Check if running with admin privileges
        $isAdmin = CheckAndElevate -ElevateIfNotAdmin $false

        if ($isAdmin) {
            Write-EnhancedLog -Message "Script is running with administrative privileges. Exiting because this operation should be run in user context." -Level "ERROR"
            throw "This script must be run in a non-administrative user context."
        }

        $DownloadODSyncUtilParams = @{
            Destination    = (Join-Path -Path $env:USERPROFILE -ChildPath "AADMigration\Files\ODSyncUtil\ODSyncUtil.exe")
            ApiUrl         = "https://api.github.com/repos/rodneyviana/ODSyncUtil/releases/latest"
            ZipFileName    = "ODSyncUtil-64-bit.zip"
            ExecutableName = "ODSyncUtil.exe"
            MaxRetries     = 3
        }
        Download-ODSyncUtil @DownloadODSyncUtilParams

        # Define the log folder path in the user's profile directory
        $logFolder = Join-Path -Path $env:USERPROFILE -ChildPath $LogFolderName

        # Define the status file path within the log folder
        $statusFile = Join-Path -Path $logFolder -ChildPath $StatusFileName

        # Ensure the log directory exists
        if (-not (Test-Path -Path $logFolder)) {
            Write-EnhancedLog -Message "Creating log folder at $logFolder" -Level "INFO"
            New-Item -Path $logFolder -ItemType Directory | Out-Null
        }
    }

    Process {
        try {
            # Define the full path to Get-ODStatus.ps1
            $getODStatusScript = Join-Path -Path $ScriptPath -ChildPath "Get-ODStatus.ps1"
    
            if (-not (Test-Path $getODStatusScript)) {
                $errorMessage = "The specified script path does not contain Get-ODStatus.ps1: $getODStatusScript"
                Write-EnhancedLog -Message $errorMessage -Level "Critical"
                throw $errorMessage
            }
    
            # Temporarily change location to ScriptPath
            Write-EnhancedLog -Message "Changing to script directory: $ScriptPath" -Level "INFO"
            Push-Location -Path $ScriptPath
    
            # Run the Get-ODStatus.ps1 script and capture the output as a PowerShell object
            Write-EnhancedLog -Message "Executing $getODStatusScript with non-elevated privileges" -Level "INFO"
            $status = . $getODStatusScript
    
            # Filter the JSON data to match the currently logged-in user
            $CurrentUser = "$env:COMPUTERNAME\$env:USERNAME"
            Write-EnhancedLog -Message "Current user detected: $CurrentUser" -Level "INFO"

            $filteredStatus = $status | Where-Object { $_.UserName -eq $CurrentUser }
    
            # Convert the filtered output directly to JSON and save it
            if ($filteredStatus) {
                Write-EnhancedLog -Message "Saving filtered OneDrive sync status to $statusFile" -Level "INFO"
                $filteredStatus | ConvertTo-Json -Depth 3 | Out-File -FilePath $statusFile -Force -Encoding utf8
            }
            else {
                Write-EnhancedLog -Message "No matching data found for user: $CurrentUser" -Level "WARNING"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Check-ODSyncUtilStatus function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
        finally {
            # Return to the original location
            Pop-Location
            Write-EnhancedLog -Message "Returned to the original directory" -Level "INFO"
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Check-ODSyncUtilStatus function" -Level "Notice"
    }
}
#EndRegion '.\Public\Check-ODSyncUtilStatus.ps1' 123
#Region '.\Public\Check-PackageAccount.ps1' -1

function Check-PackageAccount {
    <#
    .SYNOPSIS
        Prompts the user to check if the PPKG GUID account created by Windows Configuration Designer (WCD) is created in Entra ID and if it's excluded from all Conditional Access policies.
 
    .DESCRIPTION
        This function is designed to support administrators managing Provisioning Packages (PPKGs) created using Windows Configuration Designer (WCD), as detailed in the official Microsoft documentation: https://learn.microsoft.com/en-us/windows/configuration/provisioning-packages/provisioning-create-package.
        The function checks if a package account, generated during PPKG creation with an enrollment token for Entra ID, exists in Entra ID and verifies if it's excluded from all Conditional Access policies (CAPs).
        If the package_GUID account is not excluded from all CAPs, the `install-ppkg.ps1` function will encounter issues during the installation process.
 
    .PARAMETER PackageGuid
        The GUID of the package, used to construct the account email.
 
    .PARAMETER Domain
        The domain associated with the account in Entra ID.
 
    .OUTPUTS
        System.String
        A message indicating whether the account was found and if it's excluded from Conditional Access policies.
 
    .EXAMPLE
        Check-PackageAccount -PackageGuid "75cc34e6-141c-4577-8792-c238a4293408" -Domain "ictc-ctic.ca"
        This will check if the account "package_75cc34e6-141c-4577-8792-c238a4293408@ictc-ctic.ca" exists in Entra ID and is excluded from all Conditional Access policies.
 
    .NOTES
        Version: 1.2
        Author: Abdullah Ollivierre
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "The GUID of the package.")]
        [string]$PackageGuid,

        [Parameter(Mandatory = $true, HelpMessage = "The domain associated with the account in Entra ID.")]
        [string]$Domain
    )

    Begin {
        Write-EnhancedLog -Message "Initializing Check-PackageAccount function." -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Construct the package account email
            $packageAccount = "package_$PackageGuid@$Domain"
            Write-EnhancedLog -Message "Checking account: $packageAccount" -Level "INFO"

            # Prompt the user to proceed with the check
            $userResponse = Read-Host "Would you like to check if the account $packageAccount is created in Entra ID and is excluded from all Conditional Access policies? (Y/N)"
            if ($userResponse -ne 'Y') {
                Write-Host "Operation canceled by the user." -ForegroundColor Yellow
                return
            }


            # Prompt user for next steps
            $scriptOption = Read-Host "Would you like to run your own logic (type '1') or run the web script (type '2')?"
            if ($scriptOption -eq '1') {
                Write-Host "Running your own logic..." -ForegroundColor Cyan
                # User's logic here

                
                # Check if the account exists in Entra ID (simulated with a placeholder command)
                $accountExists = Get-MgUser -UserId $packageAccount -ErrorAction SilentlyContinue
                if ($null -eq $accountExists) {
                    Write-EnhancedLog -Message "Account $packageAccount does not exist in Entra ID." -Level "ERROR"
                    throw "Account not found."
                }

                Write-EnhancedLog -Message "Account $packageAccount exists in Entra ID." -Level "INFO"

                # Check if the account is excluded from all Conditional Access policies
                $excludedPolicies = Get-MgConditionalAccessPolicy | Where-Object {
                    $_.Conditions.Users.Exclude.Contains($packageAccount)
                }

                if ($excludedPolicies.Count -eq 0) {
                    Write-EnhancedLog -Message "Account $packageAccount is NOT excluded from any Conditional Access policies." -Level "WARNING"
                    Write-Host "Warning: The account is not excluded from any Conditional Access policies. The install-ppkg account will encounter issues during installation." -ForegroundColor Red
                }
                else {
                    Write-EnhancedLog -Message "Account $packageAccount is excluded from the following Conditional Access policies: $($excludedPolicies.DisplayName -join ', ')" -Level "INFO"
                    Write-Host "The account is excluded from the following Conditional Access policies: $($excludedPolicies.DisplayName -join ', ')" -ForegroundColor Green
                }

            }
            elseif ($scriptOption -eq '2') {
                Write-Host "Warning: The web script is not tested and relies on external dependencies. Please review the notes in C:\code\IntuneDeviceMigration\DeviceMigration\Scripts\Beta\Confirm-BreakGlassConditionalAccessExclusions.ps1 before proceeding." -ForegroundColor Yellow
                $confirmRun = Read-Host "Do you want to proceed with running the web script? (Y/N)"
                if ($confirmRun -eq 'Y') {
                    Write-Host "Downloading and running the web script..." -ForegroundColor Cyan
                    $scriptUrl = "https://raw.githubusercontent.com/thetolkienblackguy/EntraIdManagement/main/Confirm-BreakGlassConditionalAccessExclusions/Confirm-BreakGlassConditionalAccessExclusions.ps1"
                    $scriptContent = Invoke-WebRequest -Uri $scriptUrl -UseBasicParsing | Select-Object -ExpandProperty Content
                    Invoke-Expression $scriptContent
                }
                else {
                    Write-Host "Operation canceled by the user." -ForegroundColor Yellow
                }
            }
            else {
                Write-Host "Invalid option selected. Operation canceled." -ForegroundColor Yellow
            }

            Write-EnhancedLog -Message "Check-PackageAccount function completed successfully." -Level "NOTICE"
        }
        catch {
            Write-EnhancedLog -Message "Error occurred in Check-PackageAccount function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            Write-EnhancedLog -Message "Exiting Check-PackageAccount function." -Level "NOTICE"
        }
    }

    End {
        Write-EnhancedLog -Message "Check-PackageAccount function has fully completed." -Level "NOTICE"
    }
}

# # Example usage of the Check-PackageAccount function
# try {
# # Define the package GUID and domain
# $packageGuid = "75cc34e6-141c-4577-8792-c238a4293408"
# $domain = "ictc-ctic.ca"

# # Invoke the Check-PackageAccount function
# Check-PackageAccount -PackageGuid $packageGuid -Domain $domain
# }
# catch {
# Write-Host "An error occurred during the account check: $($_.Exception.Message)" -ForegroundColor Red
# }
#EndRegion '.\Public\Check-PackageAccount.ps1' 135
#Region '.\Public\Clear-OneDriveCache.ps1' -1

function Clear-OneDriveCache {
    <#
    .SYNOPSIS
    Clears the OneDrive cache.
   
    .DESCRIPTION
    The Clear-OneDriveCache function clears the OneDrive cache by restarting the OneDrive process.
   
    .EXAMPLE
    Clear-OneDriveCache
    Clears the OneDrive cache by restarting the OneDrive process.
    #>

  
    [CmdletBinding()]
    param ()
  
    Begin {
        Write-EnhancedLog -Message "Starting Clear-OneDriveCache function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
  
    Process {
        try {
            Write-EnhancedLog -Message "Searching for OneDrive executable path" -Level "INFO"
            $oneDrivePath = Find-OneDrivePath

            if ($oneDrivePath) {
                Write-EnhancedLog -Message "Restarting OneDrive process to clear cache" -Level "INFO"
                Stop-Process -Name "OneDrive" -Force -ErrorAction SilentlyContinue
                Start-Process -FilePath $oneDrivePath -ErrorAction Stop
                Write-EnhancedLog -Message "Successfully restarted OneDrive process" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "OneDrive executable not found in any known locations" -Level "WARNING"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Clear-OneDriveCache function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
  
    End {
        Write-EnhancedLog -Message "Exiting Clear-OneDriveCache function" -Level "Notice"
    }
}

# Example usage
# Clear-OneDriveCache
#EndRegion '.\Public\Clear-OneDriveCache.ps1' 51
#Region '.\Public\Create-EventLogSource.ps1' -1

function Create-EventLogSource {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$LogName,
        [Parameter(Mandatory = $true)]
        [string]$Source
    )

    Begin {
        Write-EnhancedLog -Message "Starting Create-EventLogSource function" -Level "INFO"
        Log-Params -Params @{
            LogName = $LogName
            Source  = $Source
        }
    }

    Process {
        try {
            if (-not (Get-EventLog -LogName $LogName -Source $Source -ErrorAction SilentlyContinue)) {
                New-EventLog -LogName $LogName -Source $Source -ErrorAction Stop
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while creating the event log source: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-EventLogSource function" -Level "INFO"
    }
}


# $CreateEventLogSourceParams = @{
# LogName = "Application"
# Source = "AAD_Migration_Script"
# }

# Create-EventLogSource @CreateEventLogSourceParams
#EndRegion '.\Public\Create-EventLogSource.ps1' 41
#Region '.\Public\Create-OneDriveCacheClearTask.ps1' -1

function Create-OneDriveCacheClearTask {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TaskPath,
        [Parameter(Mandatory = $true)]
        [string]$TaskName,
        [Parameter(Mandatory = $true)]
        [string]$ScriptDirectory,
        [Parameter(Mandatory = $true)]
        [string]$ScriptName,
        [Parameter(Mandatory = $true)]
        [string]$TaskArguments,
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionDuration,
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionInterval,
        [Parameter(Mandatory = $true)]
        [string]$TaskPrincipalGroupId,
        [Parameter(Mandatory = $true)]
        [string]$PowerShellPath,
        [Parameter(Mandatory = $true)]
        [string]$TaskDescription,
        [Parameter(Mandatory = $true)]
        [switch]$AtLogOn
    )

    Begin {
        Write-EnhancedLog -Message "Starting Create-OneDriveCacheClearTask function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Unregister the task if it exists
            Unregister-ScheduledTaskWithLogging -TaskName $TaskName

            $arguments = $TaskArguments.Replace("{ScriptPath}", "$ScriptDirectory\$ScriptName")

            $actionParams = @{
                Execute  = $PowerShellPath
                Argument = $arguments
            }
            $action = New-ScheduledTaskAction @actionParams

            $triggerParams = @{
                AtLogOn = $AtLogOn
            }
            
            $trigger = New-ScheduledTaskTrigger @triggerParams

            $principalParams = @{
                GroupId = $TaskPrincipalGroupId
            }
            $principal = New-ScheduledTaskPrincipal @principalParams

            $registerTaskParams = @{
                Principal   = $principal
                Action      = $action
                Trigger     = $trigger
                TaskName    = $TaskName
                Description = $TaskDescription
                TaskPath    = $TaskPath
            }
            $Task = Register-ScheduledTask @registerTaskParams

            $Task.Triggers.Repetition.Duration = $TaskRepetitionDuration
            $Task.Triggers.Repetition.Interval = $TaskRepetitionInterval
            $Task | Set-ScheduledTask
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while creating the OneDrive cache clear task: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-OneDriveCacheClearTask function" -Level "Notice"
    }
}

# # Example usage with splatting
# $CreateOneDriveCacheClearTaskParams = @{
# TaskPath = "OneDriveTasks"
# TaskName = "Clear OneDrive Cache"
# ScriptDirectory = "C:\Scripts"
# ScriptName = "Clear-OneDriveCache.ps1"
# TaskArguments = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
# TaskRepetitionDuration = "P1D"
# TaskRepetitionInterval = "PT30M"
# TaskPrincipalGroupId = "BUILTIN\Users"
# PowerShellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
# TaskDescription = "Clears the OneDrive cache by restarting the OneDrive process"
# AtLogOn = $true
# }

# Create-OneDriveCacheClearTask @CreateOneDriveCacheClearTaskParams
#EndRegion '.\Public\Create-OneDriveCacheClearTask.ps1' 98
#Region '.\Public\Create-OneDriveRemediationTask.ps1' -1

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

        [Parameter(Mandatory = $true)]
        [string]$ScheduledTaskName,

        [Parameter(Mandatory = $true)]
        [string]$ScheduledTaskDescription,

        [Parameter(Mandatory = $false)]
        [string]$ScheduledTaskArgumentList
    )

    Begin {
        Write-EnhancedLog -Message "Starting Create-OneDriveRemediationTask function" -Level "INFO"
        Log-Params -Params @{
            OneDriveExePath            = $OneDriveExePath
            ScheduledTaskName          = $ScheduledTaskName
            ScheduledTaskDescription   = $ScheduledTaskDescription
            ScheduledTaskArgumentList  = $ScheduledTaskArgumentList
        }
    }

    Process {
        try {
            # $userId = (Get-WmiObject -Class Win32_ComputerSystem).UserName
            $userId = $env:UserName
            if (-not $userId) {
                throw "Unable to retrieve the current user ID."
            }

            Write-EnhancedLog -Message "User ID retrieved: $userId" -Level "INFO"

            $actionParams = @{
                Execute  = $OneDriveExePath
            }
            if ($ScheduledTaskArgumentList) {
                $actionParams.Argument = $ScheduledTaskArgumentList
            }
            $action = New-ScheduledTaskAction @actionParams

            $trigger = New-ScheduledTaskTrigger -AtLogOn

            $principalParams = @{
                UserId = $userId
            }
            $principal = New-ScheduledTaskPrincipal @principalParams

            $taskParams = @{
                Action      = $action
                Trigger     = $trigger
                Principal   = $principal
                TaskName    = $ScheduledTaskName
                Description = $ScheduledTaskDescription
                Force       = $true
            }
            $task = Register-ScheduledTask @taskParams

            Start-ScheduledTask -TaskName $ScheduledTaskName

            # $DBG
            Start-Sleep -Seconds 5
            Unregister-ScheduledTask -TaskName $ScheduledTaskName -Confirm:$false
        } catch {
            Write-EnhancedLog -Message "An error occurred in Create-OneDriveRemediationTask function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-OneDriveRemediationTask function" -Level "INFO"
    }
}


# $CreateOneDriveRemediationTaskParams = @{
# OneDriveExePath = "C:\Program Files\Microsoft OneDrive\OneDrive.exe"
# ScheduledTaskName = "OneDriveRemediation"
# ScheduledTaskDescription = "Restart OneDrive to kick off KFM sync"
# ScheduledTaskArgumentList = ""
# }

# Create-OneDriveRemediationTask @CreateOneDriveRemediationTaskParams
#EndRegion '.\Public\Create-OneDriveRemediationTask.ps1' 87
#Region '.\Public\Create-OneDriveSyncUtilStatusTask.ps1' -1

# function Create-OneDriveSyncUtilStatusTask {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true)]
# [string]$TaskPath,
# [Parameter(Mandatory = $true)]
# [string]$TaskName,
# [Parameter(Mandatory = $true)]
# [string]$ScriptDirectory,
# [Parameter(Mandatory = $true)]
# [string]$ScriptName,
# [Parameter(Mandatory = $true)]
# [string]$TaskArguments,
# [Parameter(Mandatory = $true)]
# [string]$TaskRepetitionDuration,
# [Parameter(Mandatory = $true)]
# [string]$TaskRepetitionInterval,
# [Parameter(Mandatory = $true)]
# [string]$TaskPrincipalGroupId,
# [Parameter(Mandatory = $true)]
# [string]$PowerShellPath,
# [Parameter(Mandatory = $true)]
# [string]$TaskDescription,
# [Parameter(Mandatory = $true)]
# [switch]$AtLogOn

# )

# Begin {
# Write-EnhancedLog -Message "Starting Create-OneDriveSyncUtilStatusTask function" -Level "Notice"
# Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# }

# Process {
# try {
# # Unregister the task if it exists
# Unregister-ScheduledTaskWithLogging -TaskName $TaskName

# $arguments = $TaskArguments.Replace("{ScriptPath}", "$ScriptDirectory\$ScriptName")

# $actionParams = @{
# Execute = $PowerShellPath
# Argument = $arguments
# }
# $action = New-ScheduledTaskAction @actionParams

# $triggerParams = @{
# AtLogOn = $AtLogOn
# }
            
# $trigger = New-ScheduledTaskTrigger @triggerParams

# $principalParams = @{
# GroupId = $TaskPrincipalGroupId
# }
# $principal = New-ScheduledTaskPrincipal @principalParams

# $registerTaskParams = @{
# Principal = $principal
# Action = $action
# Trigger = $trigger
# TaskName = $TaskName
# Description = $TaskDescription
# TaskPath = $TaskPath
# }
# $Task = Register-ScheduledTask @registerTaskParams

# $Task.Triggers.Repetition.Duration = $TaskRepetitionDuration
# $Task.Triggers.Repetition.Interval = $TaskRepetitionInterval
# $Task | Set-ScheduledTask
# }
# catch {
# Write-EnhancedLog -Message "An error occurred while creating the OneDrive sync status task: $($_.Exception.Message)" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# }
# }

# End {
# Write-EnhancedLog -Message "Exiting Create-OneDriveSyncUtilStatusTask function" -Level "Notice"
# }
# }





# # # # Example usage with splatting
# $CreateOneDriveSyncUtilStatusTask = @{
# TaskPath = "AAD Migration"
# TaskName = "AADM Get OneDrive Sync Status"
# ScriptDirectory = "C:\ProgramData\AADMigration\Scripts"
# ScriptName = "Check-OneDriveSyncStatus.ps1"
# TaskArguments = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
# TaskRepetitionDuration = "P1D"
# TaskRepetitionInterval = "PT30M"
# TaskPrincipalGroupId = "BUILTIN\Users"
# PowerShellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
# TaskDescription = "Get current OneDrive Sync Status and write to event log"
# AtLogOn = $true
# }

# Create-OneDriveSyncUtilStatusTask @CreateOneDriveSyncUtilStatusTask







function Create-OneDriveSyncUtilStatusTask {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TaskPath,
        [Parameter(Mandatory = $true)]
        [string]$TaskName,
        [Parameter(Mandatory = $true)]
        [string]$ScriptDirectory,
        [Parameter(Mandatory = $true)]
        [string]$ScriptName,
        [Parameter(Mandatory = $true)]
        [string]$TaskArguments,
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionDuration,
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionInterval,
        [Parameter(Mandatory = $false)]
        [string]$TaskPrincipalGroupId,  # This will be optional now
        [Parameter(Mandatory = $true)]
        [string]$PowerShellPath,
        [Parameter(Mandatory = $true)]
        [string]$TaskDescription,
        [Parameter(Mandatory = $true)]
        [switch]$AtLogOn,
        [Parameter(Mandatory = $false)]
        [switch]$UseCurrentUser  # Add a switch to use the current logged-in user
    )

    Begin {
        Write-EnhancedLog -Message "Starting Create-OneDriveSyncUtilStatusTask function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Unregister the task if it exists
            Unregister-ScheduledTaskWithLogging -TaskName $TaskName

            $arguments = $TaskArguments.Replace("{ScriptPath}", "$ScriptDirectory\$ScriptName")

            $actionParams = @{
                Execute  = $PowerShellPath
                Argument = $arguments
            }
            $action = New-ScheduledTaskAction @actionParams

            $triggerParams = @{
                AtLogOn = $AtLogOn
            }
            
            $trigger = New-ScheduledTaskTrigger @triggerParams

            # Determine whether to use GroupId or Current Logged-in User
            if ($UseCurrentUser) {
                $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
                $principalParams = @{
                    UserId = $currentUser
                }
                Write-EnhancedLog -Message "Using current logged-in user: $currentUser" -Level "INFO"
            }
            else {
                $principalParams = @{
                    GroupId = $TaskPrincipalGroupId
                }
                Write-EnhancedLog -Message "Using group ID: $TaskPrincipalGroupId" -Level "INFO"
            }

            $principal = New-ScheduledTaskPrincipal @principalParams

            $registerTaskParams = @{
                Principal   = $principal
                Action      = $action
                Trigger     = $trigger
                TaskName    = $TaskName
                Description = $TaskDescription
                TaskPath    = $TaskPath
            }
            $Task = Register-ScheduledTask @registerTaskParams

            $Task.Triggers.Repetition.Duration = $TaskRepetitionDuration
            $Task.Triggers.Repetition.Interval = $TaskRepetitionInterval
            $Task | Set-ScheduledTask
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while creating the OneDrive sync status task: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-OneDriveSyncUtilStatusTask function" -Level "Notice"
    }
}


# $CreateOneDriveSyncUtilStatusTask = @{
# TaskPath = "AAD Migration"
# TaskName = "AADM Get OneDrive Sync Status"
# ScriptDirectory = "C:\ProgramData\AADMigration\Scripts"
# ScriptName = "Check-OneDriveSyncStatus.ps1"
# TaskArguments = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
# TaskRepetitionDuration = "P1D"
# TaskRepetitionInterval = "PT30M"
# PowerShellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
# TaskDescription = "Get current OneDrive Sync Status and write to event log"
# AtLogOn = $true
# UseCurrentUser = $true # Specify to use the current user
# }

# Create-OneDriveSyncUtilStatusTask @CreateOneDriveSyncUtilStatusTask
#EndRegion '.\Public\Create-OneDriveSyncUtilStatusTask.ps1' 221
#Region '.\Public\Create-PPKG.ps1' -1

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

        [Parameter(Mandatory = $true)]
        [string]$CustomizationXMLPath,

        [Parameter(Mandatory = $true)]
        [string]$PackagePath,

        [Parameter(Mandatory = $false)]
        [string]$ProductName,

        [Parameter(Mandatory = $false)]
        [string]$StoreFile,

        [Parameter(Mandatory = $false)]
        [string]$MSPackageRoot,

        [Parameter(Mandatory = $false)]
        [string]$OEMInputXML,

        [Parameter(Mandatory = $false)]
        [hashtable]$Variables,

        [Parameter(Mandatory = $false)]
        [bool]$Encrypted = $false,

        [Parameter(Mandatory = $false)]
        [bool]$Overwrite = $true
    )

    Begin {
        Write-EnhancedLog -Message "Starting Create-PPKG function" -Level "INFO"
        Log-Params -Params @{
            ICDPath = $ICDPath
            CustomizationXMLPath = $CustomizationXMLPath
            PackagePath = $PackagePath
            ProductName = $ProductName
            StoreFile = $StoreFile
            MSPackageRoot = $MSPackageRoot
            OEMInputXML = $OEMInputXML
            Variables = $Variables
            Encrypted = $Encrypted
            Overwrite = $Overwrite
        }

        # Ensure ICD.exe exists
        if (-not (Test-Path -Path $ICDPath)) {
            throw "ICD.exe not found at: $ICDPath"
        }

        # Ensure Customization XML file exists
        if (-not (Test-Path -Path $CustomizationXMLPath)) {
            throw "Customization XML file not found at: $CustomizationXMLPath"
        }
    }

    Process {
        try {
            # Build the command line arguments using a list
            $ICD_args = [System.Collections.Generic.List[string]]::new()
            $ICD_args.Add("/Build-ProvisioningPackage")
            $ICD_args.Add("/CustomizationXML:`"$CustomizationXMLPath`"")
            $ICD_args.Add("/PackagePath:`"$PackagePath`"")

            if ($Encrypted) {
                $ICD_args.Add("+Encrypted")
            } else {
                $ICD_args.Add("-Encrypted")
            }

            if ($Overwrite) {
                $ICD_args.Add("+Overwrite")
            } else {
                $ICD_args.Add("-Overwrite")
            }

            if ($ProductName) {
                $ICD_args.Add("/ProductName:`"$ProductName`"")
            }

            if ($StoreFile) {
                $ICD_args.Add("/StoreFile:`"$StoreFile`"")
            }

            if ($MSPackageRoot) {
                $ICD_args.Add("/MSPackageRoot:`"$MSPackageRoot`"")
            }

            if ($OEMInputXML) {
                $ICD_args.Add("/OEMInputXML:`"$OEMInputXML`"")
            }

            if ($Variables) {
                foreach ($key in $Variables.Keys) {
                    $ICD_args.Add("/Variables:`"$key=$($Variables[$key])`"")
                }
            }

            $ICD_args_string = $ICD_args -join " "
            Write-EnhancedLog -Message "Running ICD.exe with arguments: $ICD_args_string" -Level "INFO"
            Start-Process -FilePath $ICDPath -ArgumentList $ICD_args_string -Wait -NoNewWindow

        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Create-PPKG function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-PPKG function" -Level "INFO"
    }
}

# Example usage
# $ppkgParams = @{
# ICDPath = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Imaging and Configuration Designer\x86\ICD.exe"
# CustomizationXMLPath = "C:\code\CB\Entra\DeviceMigration\Files\customizations.xml"
# PackagePath = "C:\code\CB\Entra\DeviceMigration\Files\ProvisioningPackage.ppkg"
# Encrypted = $false
# Overwrite = $true
# }

# Create-PPKG @ppkgParams
#EndRegion '.\Public\Create-PPKG.ps1' 128
#Region '.\Public\Create-UserFileBackupTask.ps1' -1

# function Create-UserFileBackupTask {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true)]
# [string]$TaskPath,
# [Parameter(Mandatory = $true)]
# [string]$TaskName,
# [Parameter(Mandatory = $true)]
# [string]$ScriptDirectory,
# [Parameter(Mandatory = $true)]
# [string]$ScriptName,
# [Parameter(Mandatory = $true)]
# [string]$TaskArguments,
# [Parameter(Mandatory = $true)]
# [string]$TaskRepetitionDuration,
# [Parameter(Mandatory = $true)]
# [string]$TaskRepetitionInterval,
# [Parameter(Mandatory = $true)]
# [string]$TaskPrincipalGroupId,
# [Parameter(Mandatory = $true)]
# [string]$PowerShellPath,
# [Parameter(Mandatory = $true)]
# [string]$TaskDescription,
# [Parameter(Mandatory = $true)]
# [switch]$AtLogOn

# )

# Begin {
# Write-EnhancedLog -Message "Starting Create-UserFileBackupTask function" -Level "Notice"
# Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# }

# Process {
# try {
# # Unregister the task if it exists
# Unregister-ScheduledTaskWithLogging -TaskName $TaskName

# $arguments = $TaskArguments.Replace("{ScriptPath}", "$ScriptDirectory\$ScriptName")

# $actionParams = @{
# Execute = $PowerShellPath
# Argument = $arguments
# }
# $action = New-ScheduledTaskAction @actionParams

# $triggerParams = @{
# AtLogOn = $AtLogOn
# }
            
# $trigger = New-ScheduledTaskTrigger @triggerParams

# $principalParams = @{
# GroupId = $TaskPrincipalGroupId
# }
# $principal = New-ScheduledTaskPrincipal @principalParams

# $registerTaskParams = @{
# Principal = $principal
# Action = $action
# Trigger = $trigger
# TaskName = $TaskName
# Description = $TaskDescription
# TaskPath = $TaskPath
# }
# $Task = Register-ScheduledTask @registerTaskParams

# $Task.Triggers.Repetition.Duration = $TaskRepetitionDuration
# $Task.Triggers.Repetition.Interval = $TaskRepetitionInterval
# $Task | Set-ScheduledTask
# }
# catch {
# Write-EnhancedLog -Message "An error occurred while creating the OneDrive sync status task: $($_.Exception.Message)" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# }
# }

# End {
# Write-EnhancedLog -Message "Exiting Create-UserFileBackupTask function" -Level "Notice"
# }
# }


function Create-UserFileBackupTask {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TaskPath,
        [Parameter(Mandatory = $true)]
        [string]$TaskName,
        [Parameter(Mandatory = $true)]
        [string]$ScriptDirectory,
        [Parameter(Mandatory = $true)]
        [string]$ScriptName,
        [Parameter(Mandatory = $true)]
        [string]$TaskArguments,
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionDuration,
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionInterval,
        [Parameter(Mandatory = $false)]
        [string]$TaskPrincipalGroupId,  # Optional now
        [Parameter(Mandatory = $true)]
        [string]$PowerShellPath,
        [Parameter(Mandatory = $true)]
        [string]$TaskDescription,
        [Parameter(Mandatory = $true)]
        [switch]$AtLogOn,
        [Parameter(Mandatory = $false)]
        [switch]$UseCurrentUser  # Switch to use the current user
    )

    Begin {
        Write-EnhancedLog -Message "Starting Create-UserFileBackupTask function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Unregister the task if it exists
            Unregister-ScheduledTaskWithLogging -TaskName $TaskName

            $arguments = $TaskArguments.Replace("{ScriptPath}", "$ScriptDirectory\$ScriptName")

            $actionParams = @{
                Execute  = $PowerShellPath
                Argument = $arguments
            }
            $action = New-ScheduledTaskAction @actionParams

            $triggerParams = @{
                AtLogOn = $AtLogOn
            }
            
            $trigger = New-ScheduledTaskTrigger @triggerParams

            # Determine whether to use GroupId or Current Logged-in User
            if ($UseCurrentUser) {
                $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
                $principalParams = @{
                    UserId = $currentUser
                }
                Write-EnhancedLog -Message "Using current logged-in user: $currentUser" -Level "INFO"
            }
            else {
                $principalParams = @{
                    GroupId = $TaskPrincipalGroupId
                }
                Write-EnhancedLog -Message "Using group ID: $TaskPrincipalGroupId" -Level "INFO"
            }

            $principal = New-ScheduledTaskPrincipal @principalParams

            $registerTaskParams = @{
                Principal   = $principal
                Action      = $action
                Trigger     = $trigger
                TaskName    = $TaskName
                Description = $TaskDescription
                TaskPath    = $TaskPath
            }
            $Task = Register-ScheduledTask @registerTaskParams

            $Task.Triggers.Repetition.Duration = $TaskRepetitionDuration
            $Task.Triggers.Repetition.Interval = $TaskRepetitionInterval
            $Task | Set-ScheduledTask
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while creating the User File Backup task: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-UserFileBackupTask function" -Level "Notice"
    }
}




# # # # Example usage with splatting
# $CreateUserFileBackupTaskParams = @{
# TaskPath = "AAD Migration"
# TaskName = "Backup User Files"
# ScriptDirectory = "C:\ProgramData\AADMigration\Scripts"
# ScriptName = "BackupUserFiles.ps1"
# TaskArguments = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
# TaskRepetitionDuration = "P1D"
# TaskRepetitionInterval = "PT30M"
# TaskPrincipalGroupId = "BUILTIN\Users"
# PowerShellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
# TaskDescription = "Backup User Files to OneDrive"
# AtLogOn = $true
# }

# Create-UserFileBackupTask @CreateUserFileBackupTaskParams
#EndRegion '.\Public\Create-UserFileBackupTask.ps1' 198
#Region '.\Public\Disable-LocalUserAccounts.ps1' -1

function Disable-LocalUserAccounts {
    <#
    .SYNOPSIS
    Disables all enabled local user accounts except for default accounts.
   
    .DESCRIPTION
    The Disable-LocalUserAccounts function disables all enabled local user accounts except for default accounts.
   
    .EXAMPLE
    Disable-LocalUserAccounts
    Disables all enabled local user accounts except for default accounts.
    #>

  
    [CmdletBinding()]
    param ()
  
    Begin {
        Write-EnhancedLog -Message "Starting Disable-LocalUserAccounts function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
  
    Process {
        try {
            # Get all enabled local user accounts except for default accounts
            $users = Get-LocalUser | Where-Object { $_.Enabled -eq $true -and $_.Name -notlike 'default*' }
            
            foreach ($user in $users) {
                Write-EnhancedLog -Message "Disabling local user account: $($user.Name)" -Level "INFO"
                Disable-LocalUser -Name $user.Name -ErrorAction Stop
                Write-EnhancedLog -Message "Successfully disabled local user account: $($user.Name)" -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Disable-LocalUserAccounts function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
  
    End {
        Write-EnhancedLog -Message "Exiting Disable-LocalUserAccounts function" -Level "Notice"
    }
}

# Example usage
# Disable-LocalUserAccounts
#EndRegion '.\Public\Disable-LocalUserAccounts.ps1' 47
#Region '.\Public\Disable-OOBEPrivacy.ps1' -1

function Disable-OOBEPrivacy {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$OOBERegistryPath,

        [Parameter(Mandatory = $true)]
        [string]$OOBEName,

        [Parameter(Mandatory = $true)]
        [string]$OOBEValue,

        [Parameter(Mandatory = $true)]
        [string]$AnimationRegistryPath,

        [Parameter(Mandatory = $true)]
        [string]$AnimationName,

        [Parameter(Mandatory = $true)]
        [string]$AnimationValue,

        [Parameter(Mandatory = $true)]
        [string]$LockRegistryPath,

        [Parameter(Mandatory = $true)]
        [string]$LockName,

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

    Begin {
        Write-EnhancedLog -Message "Starting Disable-OOBEPrivacy function" -Level "INFO"
        Log-Params -Params @{
            OOBERegistryPath      = $OOBERegistryPath
            OOBEName              = $OOBEName
            OOBEValue             = $OOBEValue
            AnimationRegistryPath = $AnimationRegistryPath
            AnimationName         = $AnimationName
            AnimationValue        = $AnimationValue
            LockRegistryPath      = $LockRegistryPath
            LockName              = $LockName
            LockValue             = $LockValue
        }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Disabling privacy experience" -Level "INFO"
            if (-not (Test-Path -Path $OOBERegistryPath)) {
                New-Item -Path $OOBERegistryPath -Force | Out-Null
            }
            New-ItemProperty -Path $OOBERegistryPath -Name $OOBEName -Value $OOBEValue -PropertyType DWORD -Force -Verbose

            Write-EnhancedLog -Message "Disabling first logon animation" -Level "INFO"
            if (-not (Test-Path -Path $AnimationRegistryPath)) {
                New-Item -Path $AnimationRegistryPath -Force | Out-Null
            }
            New-ItemProperty -Path $AnimationRegistryPath -Name $AnimationName -Value $AnimationValue -PropertyType DWORD -Force -Verbose

            Write-EnhancedLog -Message "Removing lock screen" -Level "INFO"
            if (-not (Test-Path -Path $LockRegistryPath)) {
                New-Item -Path $LockRegistryPath -Force | Out-Null
            }
            New-ItemProperty -Path $LockRegistryPath -Name $LockName -Value $LockValue -PropertyType DWORD -Force -Verbose
        } catch {
            Write-EnhancedLog -Message "An error occurred while disabling OOBE privacy: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Disable-OOBEPrivacy function" -Level "INFO"
    }
}

# # Example usage with splatting
# $DisableOOBEPrivacyParams = @{
# OOBERegistryPath = 'HKLM:\Software\Policies\Microsoft\Windows\OOBE'
# OOBEName = 'DisablePrivacyExperience'
# OOBEValue = '1'
# AnimationRegistryPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
# AnimationName = 'EnableFirstLogonAnimation'
# AnimationValue = '0'
# LockRegistryPath = 'HKLM:\Software\Policies\Microsoft\Windows\Personalization'
# LockName = 'NoLockScreen'
# LockValue = '1'
# }

# Disable-OOBEPrivacy @DisableOOBEPrivacyParams
#EndRegion '.\Public\Disable-OOBEPrivacy.ps1' 91
#Region '.\Public\Disable-ScheduledTaskByPath.ps1' -1

function Disable-ScheduledTaskByPath {
    <#
    .SYNOPSIS
    Disables a scheduled task by its name and path.
 
    .DESCRIPTION
    This function disables a scheduled task specified by its task name and path.
 
    .PARAMETER TaskName
    The name of the scheduled task.
 
    .PARAMETER TaskPath
    The path of the scheduled task.
 
    .EXAMPLE
    Disable-ScheduledTaskByPath -TaskName "TaskName" -TaskPath "\Folder\"
 
    This will disable the task "TaskName" in the folder "\Folder\" in Task Scheduler.
 
    .NOTES
    This function is useful for disabling tasks located in specific subfolders within the Task Scheduler.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the name of the scheduled task.")]
        [ValidateNotNullOrEmpty()]
        [string]$TaskName,
        
        [Parameter(Mandatory = $true, HelpMessage = "Provide the path of the scheduled task.")]
        [ValidateNotNullOrEmpty()]
        [string]$TaskPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Disable-ScheduledTaskByPath function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            Write-EnhancedLog -Message "Searching for scheduled task with Name: $TaskName and Path: $TaskPath" -Level "INFO"

            # Search for the scheduled task by TaskName and TaskPath
            $Task = Get-ScheduledTask | Where-Object { $_.TaskPath -eq $TaskPath -and $_.TaskName -eq $TaskName }

            if ($Task) {
                # Disable the found scheduled task
                Write-EnhancedLog -Message "Scheduled task found: '$($Task.TaskPath + $Task.TaskName)'. Disabling the task." -Level "INFO"
                Disable-ScheduledTask -TaskName ($Task.TaskPath + $Task.TaskName)
                Write-EnhancedLog -Message "Scheduled task '$($Task.TaskPath + $Task.TaskName)' has been successfully disabled." -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Scheduled task '$TaskPath$TaskName' not found." -Level "WARNING"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while disabling the scheduled task: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            Write-EnhancedLog -Message "Exiting Disable-ScheduledTaskByPath function" -Level "NOTICE"
        }
    }

    End {
        Write-EnhancedLog -Message "Disable-ScheduledTaskByPath function completed" -Level "INFO"
    }
}
#EndRegion '.\Public\Disable-ScheduledTaskByPath.ps1' 71
#Region '.\Public\Download-ADKOffline.ps1' -1

function Download-ADKOffline {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ADKUrl = "https://go.microsoft.com/fwlink/?linkid=2271337",

        [Parameter(Mandatory = $true)]
        [string]$DownloadPath = "$env:TEMP\adksetup.exe",

        [Parameter(Mandatory = $true)]
        [string]$OfflinePath = "$env:TEMP\ADKOffline"
    )

    Begin {
        Write-EnhancedLog -Message "Starting Download-ADKOffline function" -Level "INFO"
        Log-Params -Params @{
            ADKUrl = $ADKUrl
            DownloadPath = $DownloadPath
            OfflinePath = $OfflinePath
        }
    }

    Process {
        try {
            # Download the ADK setup file
            Write-EnhancedLog -Message "Downloading ADK from: $ADKUrl to: $DownloadPath" -Level "INFO"
            Invoke-WebRequest -Uri $ADKUrl -OutFile $DownloadPath

            # Create offline path if it does not exist
            if (-not (Test-Path -Path $OfflinePath)) {
                New-Item -ItemType Directory -Path $OfflinePath -Force
            }

            # Download the ADK components for offline installation
            Write-EnhancedLog -Message "Downloading ADK components for offline installation to: $OfflinePath" -Level "INFO"
            Start-Process -FilePath $DownloadPath -ArgumentList "/quiet", "/layout $OfflinePath" -Wait -NoNewWindow
        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Download-ADKOffline function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-ADKOffline function" -Level "INFO"
    }
}

# Example usage
# $adkParams = @{
# ADKUrl = 'https://go.microsoft.com/fwlink/?linkid=2271337'
# DownloadPath = "$env:TEMP\adksetup.exe"
# OfflinePath = "$env:TEMP\ADKOffline"
# }

# Download-ADKOffline @adkParams
#EndRegion '.\Public\Download-ADKOffline.ps1' 56
#Region '.\Public\Download-InstallMDT.ps1' -1

# function Download-InstallMDT {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true)]
# [string]$Url,

# [Parameter(Mandatory = $true)]
# [string]$Destination,

# [Parameter(Mandatory = $true)]
# [string]$FilesFolder
# )

# Begin {
# Write-EnhancedLog -Message "Starting Download-Install-MDT function" -Level "INFO"
# Log-Params -Params @{
# Url = $Url
# Destination = $Destination
# FilesFolder = $FilesFolder
# }
# }

# Process {
# try {
# # Download and install Microsoft Deployment Toolkit
# Invoke-WebRequest -Uri $Url -OutFile $Destination
# Start-Process -FilePath $Destination -ArgumentList "/quiet" -Wait

# # Copy ServiceUI.exe to Files folder
# Copy-Item -Path "C:\Program Files\Microsoft Deployment Toolkit\Templates\Distribution\Tools\x64\ServiceUI.exe" -Destination $FilesFolder
# } catch {
# Write-EnhancedLog -Message "An error occurred while processing the Download-InstallMDT function: $($_.Exception.Message)" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# }
# }

# End {
# Write-EnhancedLog -Message "Exiting Download-Install-MDT function" -Level "INFO"
# }
# }

# # Example usage
# # Download-InstallMDT -Url 'https://download.microsoft.com/download/9/e/1/9e1e94ec-5463-46b7-9f3c-b225034c3a70/MDT_KB4564442.exe' -Destination 'C:\YourPath\Files\MDT.exe' -FilesFolder 'C:\YourPath\Files'
#EndRegion '.\Public\Download-InstallMDT.ps1' 44
#Region '.\Public\Download-MigrationTool.ps1' -1

function Download-MigrationTool {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Url,

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

    Begin {
        Write-EnhancedLog -Message "Starting Download-MigrationTool function" -Level "INFO"
        Log-Params -Params @{
            Url = $Url
            Destination = $Destination
        }
    }

    Process {
        try {
            # Download Migration Tool
            Invoke-WebRequest -Uri $Url -OutFile $Destination
        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Download-MigrationTool function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-MigrationTool function" -Level "INFO"
    }
}

# Example usage
# Download-MigrationTool -Url "https://example.com/tool.zip" -Destination "C:\path\to\destination"
#EndRegion '.\Public\Download-MigrationTool.ps1' 36
#Region '.\Public\Download-ODSyncUtil.ps1' -1

function Download-ODSyncUtil {
    <#
    .SYNOPSIS
    Downloads and extracts the latest ODSyncUtil from the OneDrive Sync Utility GitHub repository for Windows 11.
 
    .DESCRIPTION
    The Download-ODSyncUtil function retrieves the latest release of ODSyncUtil from the GitHub repository, downloads the ZIP file, extracts it, and places the executable in the specified destination folder.
 
    .PARAMETER Destination
    The destination folder where ODSyncUtil.exe will be stored.
 
    .PARAMETER ApiUrl
    The GitHub API URL to retrieve the latest release information.
 
    .PARAMETER ZipFileName
    The name of the ZIP file to be downloaded (e.g., "ODSyncUtil-64-bit.zip").
 
    .PARAMETER ExecutableName
    The name of the executable to be extracted from the ZIP file (e.g., "ODSyncUtil.exe").
 
    .PARAMETER MaxRetries
    The maximum number of retries for the download process.
 
    .EXAMPLE
    $params = @{
        Destination = "C:\YourPath\Files\ODSyncUtil.exe"
        ApiUrl = "https://api.github.com/repos/rodneyviana/ODSyncUtil/releases/latest"
        ZipFileName = "ODSyncUtil-64-bit.zip"
        ExecutableName = "ODSyncUtil.exe"
        MaxRetries = 3
    }
    Download-ODSyncUtil @params
    Downloads and extracts ODSyncUtil.exe to the specified destination folder.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Destination,

        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $true)]
        [string]$ZipFileName,

        [Parameter(Mandatory = $true)]
        [string]$ExecutableName,

        [Parameter(Mandatory = $false)]
        [int]$MaxRetries = 3
    )

    Begin {
        Write-EnhancedLog -Message "Starting Download-ODSyncUtil function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

    }

    Process {
        try {
            # Get the latest release info from GitHub
            Write-EnhancedLog -Message "Retrieving latest release info from GitHub API: $ApiUrl" -Level "INFO"
            $releaseInfo = Invoke-RestMethod -Uri $ApiUrl

            # Find the download URL for the specified ZIP file
            $downloadUrl = $releaseInfo.assets | Where-Object { $_.name -eq $ZipFileName } | Select-Object -ExpandProperty browser_download_url

            if (-not $downloadUrl) {
                $errorMessage = "No matching file found for $ZipFileName"
                Write-EnhancedLog -Message $errorMessage -Level "Critical"
                throw $errorMessage
            }

            # Define the ZIP file path
            $zipFilefolder = Split-Path -Path $Destination -Parent
            $zipFilePath = Join-Path -Path (Split-Path -Path $Destination -Parent) -ChildPath $ZipFileName


            #Remove the Existing Zip Folder Folder if found
            if (Test-Path -Path $zipFilefolder) {
                Write-EnhancedLog -Message "Found $zipFilefolder. Removing it..." -Level "INFO"
                try {
                    Remove-Item -Path $zipFilefolder -Recurse -Force
                    Write-EnhancedLog -Message "Successfully removed $zipFilefolder." -Level "INFO"
                }
                catch {
                    Write-EnhancedLog -Message "Failed to remove $zipFilefolder $($_.Exception.Message)" -Level "ERROR"
                    Handle-Error -ErrorRecord $_
                    throw $_
                }
            }
            else {
                Write-EnhancedLog -Message "$zipFilefolder not found. No action required." -Level "INFO"
            }

            # $DBG


            # Define the splatting parameters for the download
            $downloadParams = @{
                Source      = $downloadUrl
                Destination = $zipFilePath
                MaxRetries  = $MaxRetries
            }

            Write-EnhancedLog -Message "Downloading $ZipFileName from: $downloadUrl to: $zipFilePath" -Level "INFO"
            Start-FileDownloadWithRetry @downloadParams

            # Extract the executable from the ZIP file
            Write-EnhancedLog -Message "Extracting $ZipFileName to: $(Split-Path -Path $Destination -Parent)" -Level "INFO"
            Expand-Archive -Path $zipFilePath -DestinationPath (Split-Path -Path $Destination -Parent) -Force

            # Move the extracted executable to the desired location
            $extractedExePath = Join-Path -Path (Split-Path -Path $Destination -Parent) -ChildPath $ExecutableName
            if (Test-Path -Path $extractedExePath) {
                Write-EnhancedLog -Message "Moving $ExecutableName to: $Destination" -Level "INFO"
                Move-Item -Path $extractedExePath -Destination $Destination -Force

                # Remove the downloaded ZIP file and the extracted folder
                # Write-EnhancedLog -Message "Cleaning up: Removing downloaded ZIP file from $zipFilePath and extracted files from $extractedExePath" -Level "INFO"
                # Remove-Item -Path $zipFilePath -Force
                # Remove-Item -Path (Split-Path -Path $extractedExePath -Parent) -Recurse -Force
            }
            else {
                $errorMessage = "$ExecutableName not found after extraction."
                Write-EnhancedLog -Message $errorMessage -Level "Critical"
                throw $errorMessage
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Download-ODSyncUtil function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-ODSyncUtil function" -Level "Notice"
    }
}

# # # # Example usage
# $params = @{
# Destination = "C:\code\IntuneDeviceMigration\DeviceMigration\Files\ODSyncUtil\ODSyncUtil.exe"
# ApiUrl = "https://api.github.com/repos/rodneyviana/ODSyncUtil/releases/latest"
# ZipFileName = "ODSyncUtil-64-bit.zip"
# ExecutableName = "ODSyncUtil.exe"
# MaxRetries = 3
# }
# Download-ODSyncUtil @params
#EndRegion '.\Public\Download-ODSyncUtil.ps1' 152
#Region '.\Public\Download-OneDriveLib.ps1' -1

function Download-OneDriveLib {
    <#
    .SYNOPSIS
    Downloads the latest OneDriveLib.dll from the OneDrive Sync Util GitHub repository.
 
    .DESCRIPTION
    The Download-OneDriveLib function retrieves the latest release of OneDriveLib.dll from the GitHub repository of the OneDrive Sync Util and downloads it to the specified destination folder.
 
    .PARAMETER Destination
    The destination folder where OneDriveLib.dll will be stored.
 
    .PARAMETER ApiUrl
    The GitHub API URL to retrieve the latest release information.
 
    .PARAMETER FileName
    The name of the file to be downloaded (e.g., "OneDriveLib.dll").
 
    .PARAMETER MaxRetries
    The maximum number of retries for the download process.
 
    .EXAMPLE
    $params = @{
        Destination = "C:\YourPath\Files\OneDriveLib.dll"
        ApiUrl = "https://api.github.com/repos/rodneyviana/ODSyncService/releases/latest"
        FileName = "OneDriveLib.dll"
        MaxRetries = 3
    }
    Download-OneDriveLib @params
    Downloads OneDriveLib.dll to the specified destination folder.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Destination,

        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $true)]
        [string]$FileName,

        [Parameter(Mandatory = $false)]
        [int]$MaxRetries = 3
    )

    Begin {
        Write-EnhancedLog -Message "Starting Download-OneDriveLib function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Get the latest release info from GitHub
            Write-EnhancedLog -Message "Retrieving latest release info from GitHub API: $ApiUrl" -Level "INFO"
            $releaseInfo = Invoke-RestMethod -Uri $ApiUrl

            # Find the download URL for the specified file
            $downloadUrl = $releaseInfo.assets | Where-Object { $_.name -eq $FileName } | Select-Object -ExpandProperty browser_download_url

            if (-not $downloadUrl) {
                $errorMessage = "No matching file found for $FileName"
                Write-EnhancedLog -Message $errorMessage -Level "Critical"
                throw $errorMessage
            }

            # Define the splatting parameters for the download
            $downloadParams = @{
                Source      = $downloadUrl
                Destination = $Destination
                MaxRetries  = $MaxRetries
            }

            Write-EnhancedLog -Message "Downloading $FileName from: $downloadUrl to: $Destination" -Level "INFO"
            Start-FileDownloadWithRetry @downloadParams
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Download-OneDriveLib function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-OneDriveLib function" -Level "Notice"
    }
}

# # Example usage
# $params = @{
# Destination = "C:\YourPath\Files\OneDriveLib.dll"
# ApiUrl = "https://api.github.com/repos/rodneyviana/ODSyncService/releases/latest"
# FileName = "OneDriveLib.dll"
# MaxRetries = 3
# }
# Download-OneDriveLib @params
#EndRegion '.\Public\Download-OneDriveLib.ps1' 97
#Region '.\Public\Download-OneDriveSetup.ps1' -1

# Function to download OneDrive setup with retry logic
function Download-OneDriveSetup {
    <#
    .SYNOPSIS
        Downloads the OneDrive setup executable.
 
    .DESCRIPTION
        Downloads the OneDrive setup executable from the specified URL to the given destination path.
        Uses the Start-FileDownloadWithRetry function for robust download handling with retries.
 
    .PARAMETER ODSetupUri
        The URL of the OneDrive setup executable.
 
    .PARAMETER ODSetupPath
        The file path where the OneDrive setup executable will be saved.
 
    .EXAMPLE
        Download-OneDriveSetup -ODSetupUri "https://go.microsoft.com/fwlink/?linkid=844652" -ODSetupPath "C:\Temp\OneDriveSetup.exe"
 
    .NOTES
        Author: Abdullah Ollivierre
        Date: 2024-08-15
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ODSetupUri,

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

    Begin {
        Write-EnhancedLog -Message "Starting Download-OneDriveSetup function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        Write-EnhancedLog -Message "Starting Start-FileDownloadWithRetry function" -Level "NOTICE"
        Start-FileDownloadWithRetry -Source $ODSetupUri -Destination $ODSetupPath -MaxRetries 3
        Write-EnhancedLog -Message "Downloaded OneDrive setup to $ODSetupPath" -Level "INFO"
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-OneDriveSetup function" -Level "NOTICE"
    }
}
# Function to install OneDrive
#EndRegion '.\Public\Download-OneDriveSetup.ps1' 50
#Region '.\Public\Enable-LocalUserAccounts.ps1' -1

function Enable-LocalUserAccounts {
    <#
    .SYNOPSIS
    Enables all local user accounts.
   
    .DESCRIPTION
    The Enable-LocalUserAccounts function enables all local user accounts, without any filtering.
   
    .EXAMPLE
    Enable-LocalUserAccounts
    Enables all local user accounts.
    #>

  
    [CmdletBinding()]
    param ()
  
    Begin {
        Write-EnhancedLog -Message "Starting Enable-LocalUserAccounts function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
  
    Process {
        try {
            # Get all local user accounts
            $users = Get-LocalUser
            
            foreach ($user in $users) {
                Write-EnhancedLog -Message "Enabling local user account: $($user.Name)" -Level "INFO"
                Enable-LocalUser -Name $user.Name -ErrorAction Stop
                Write-EnhancedLog -Message "Successfully enabled local user account: $($user.Name)" -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Enable-LocalUserAccounts function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
  
    End {
        Write-EnhancedLog -Message "Exiting Enable-LocalUserAccounts function" -Level "Notice"
    }
}

# Example usage
# Enable-LocalUserAccounts
#EndRegion '.\Public\Enable-LocalUserAccounts.ps1' 47
#Region '.\Public\Ensure-UserInLocalAdminGroup.ps1' -1

function Add-UserToLocalAdminGroup {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Specify the username to add to the local admin group.")]
        [string]$Username
    )

    Process {
        Write-EnhancedLog -Message "Starting process to add user '$Username' to local Administrators group." -Level "NOTICE"

        # Verify if the user is already in the group using Get-EnhancedLocalGroupMembers
        $isMember = Get-EnhancedLocalGroupMembers -GroupName "Administrators" | Where-Object { $_.Account -eq $Username }

        if ($isMember) {
            Write-EnhancedLog -Message "User '$Username' is already a member of the Administrators group. No action required." -Level "INFO"
            return
        }

        # Use Add-LocalGroupMember to add the user to the local administrators group
        if ($PSCmdlet.ShouldProcess("Administrators group", "Adding user '$Username'")) {
            try {
                Add-LocalGroupMember -Group "Administrators" -Member $Username
                Write-EnhancedLog -Message "Successfully added '$Username' to the local Administrators group." -Level "SUCCESS"
            }
            catch {
                Write-EnhancedLog -Message "Failed to add user '$Username' to the local Administrators group: $($_.Exception.Message)" -Level "ERROR"
                throw
            }
        }

        Write-EnhancedLog -Message "Completed process to add user '$Username' to local Administrators group." -Level "NOTICE"
    }
}

function Create-LocalUserIfNotExists {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Specify the username to create.")]
        [string]$Username,

        [Parameter(Mandatory = $true, HelpMessage = "Specify the full Description for the new user.")]
        [string]$Description,

        [Parameter(Mandatory = $true, HelpMessage = "Specify the password for the new user.")]
        [string]$Password
    )

    Process {
        Write-EnhancedLog -Message "Checking if user '$Username' exists." -Level "NOTICE"

        # Check if running in PowerShell 7 or later
        if ($PSVersionTable.PSVersion.Major -ge 7) {
            Write-EnhancedLog -Message "Running in PowerShell 7. Importing LocalAccounts module using Windows PowerShell." -Level "NOTICE"
            try {
                # Import the LocalAccounts module from Windows PowerShell
                Import-Module -Name Microsoft.PowerShell.LocalAccounts -UseWindowsPowerShell -ErrorAction Stop
            }
            catch {
                Write-EnhancedLog -Message "Failed to import LocalAccounts module: $($_.Exception.Message)" -Level "ERROR"
                throw
            }
        }

        try {
            # Check if user exists using Get-LocalUser
            $user = Get-LocalUser -Name $Username -ErrorAction Stop
            Write-EnhancedLog -Message "User '$Username' already exists. No action required." -Level "INFO"
            return $true
        }
        catch {
            Write-EnhancedLog -Message "User '$Username' does not exist. Proceeding with creation..." -Level "NOTICE"

            if ($PSCmdlet.ShouldProcess("System", "Create local user '$Username'")) {
                try {
                    # Create the user if they do not exist
                    New-LocalUser -Name $Username -Description $Description -Password (ConvertTo-SecureString $Password -AsPlainText -Force)
                    Write-EnhancedLog -Message "Successfully created user '$Username'." -Level "SUCCESS"
                    return $true
                }
                catch {
                    Write-EnhancedLog -Message "Failed to create user '$Username': $($_.Exception.Message)" -Level "ERROR"
                    throw
                }
            }
        }

        Write-EnhancedLog -Message "Completed process to create user '$Username'." -Level "NOTICE"
        return $false
    }
}

function Ensure-UserInLocalAdminGroup {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Specify the username to manage.")]
        [string]$Username,

        [Parameter(Mandatory = $true, HelpMessage = "Specify the full Description for the new user.")]
        [string]$Description,

        [Parameter(Mandatory = $true, HelpMessage = "Specify the password for the new user.")]
        [string]$Password
    )

    Process {
        Write-EnhancedLog -Message "Ensuring user '$Username' is in the local Administrators group." -Level "NOTICE"

        # Step 1: Check if user exists, if not, create the user
        $userParams = @{
            Username    = $Username
            Description = $Description
            Password    = $Password
        }
        $userCreated = Create-LocalUserIfNotExists @userParams

        if ($userCreated) {
            # Step 2: Verifying user membership before adding to the local admin group using Get-EnhancedLocalGroupMembers
            $beforeMember = Get-EnhancedLocalGroupMembers -GroupName "Administrators" | Where-Object { $_.Account -eq $Username }

            if (-not $beforeMember) {
                Write-EnhancedLog -Message "User '$Username' is not a member of the local Administrators group." -Level "NOTICE"

                # Step 3: Add the user to the local admin group
                Add-UserToLocalAdminGroup -Username $Username
            }

            # Step 4: Verifying user membership after adding to the local admin group using Get-EnhancedLocalGroupMembers
            $afterMember = Get-EnhancedLocalGroupMembers -GroupName "Administrators" | Where-Object { $_.Account -eq $Username }

            if ($afterMember) {
                Write-EnhancedLog -Message "User '$Username' has been successfully added to the local Administrators group." -Level "SUCCESS"
            }
            else {
                Write-EnhancedLog -Message "User '$Username' was NOT added to the local Administrators group." -Level "ERROR"
            }
        }
        else {
            Write-EnhancedLog -Message "User '$Username' could not be created. Exiting process." -Level "ERROR"
        }

        Write-EnhancedLog -Message "Completed process to ensure user '$Username' is in the local Administrators group." -Level "NOTICE"
    }
}


# $params = @{
# Username = "Tempuser005"
# Description = "Temporary User 002"
# Password = "SecurePassword123!"
# }

# Ensure-UserInLocalAdminGroup @params
#EndRegion '.\Public\Ensure-UserInLocalAdminGroup.ps1' 153
#Region '.\Public\Escrow-BitLockerKey.ps1' -1

function Escrow-BitLockerKey {
    <#
    .SYNOPSIS
    Escrows the BitLocker recovery key to Azure AD.
 
    .DESCRIPTION
    The Escrow-BitLockerKey function tests if BitLocker is enabled on the specified drive, retrieves the key protector ID, and escrows the BitLocker recovery key to Azure AD.
 
    .PARAMETER DriveLetter
    The drive letter of the BitLocker protected drive.
 
    .EXAMPLE
    $params = @{
        DriveLetter = "C:"
    }
    Escrow-BitLockerKey @params
    Escrows the BitLocker recovery key for drive C: to Azure AD.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$DriveLetter
    )

    Begin {
        Write-EnhancedLog -Message "Starting Escrow-BitLockerKey function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            $bitlockerVolume = Test-Bitlocker -BitlockerDrive $DriveLetter
            $keyProtectorId = Get-KeyProtectorId -BitlockerDrive $DriveLetter
            Invoke-BitlockerEscrow -BitlockerDrive $DriveLetter -BitlockerKey $keyProtectorId
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Escrow-BitLockerKey function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Escrow-BitLockerKey function" -Level "Notice"
    }
}

# # Example usage
# $params = @{
# DriveLetter = "C:"
# }
# Escrow-BitLockerKey @params
#EndRegion '.\Public\Escrow-BitLockerKey.ps1' 54
#Region '.\Public\Execute-MigrationCleanupTasks.ps1' -1

function Execute-MigrationCleanupTasks {
    <#
    .SYNOPSIS
    Executes post-run operations for the third phase of the migration process.
   
    .DESCRIPTION
    The Execute-MigrationCleanupTasks function performs cleanup tasks after migration, including removing temporary user accounts, disabling local user accounts, removing scheduled tasks, clearing OneDrive cache, and setting registry values.
   
    .PARAMETER TempUser
    The name of the temporary user account to be removed.
   
    .PARAMETER RegistrySettings
    A hashtable of registry settings to be applied.
   
    .PARAMETER MigrationDirectories
    An array of directories to be removed as part of migration cleanup.
 
    .PARAMETER Mode
    Specifies the mode in which the script should run. Options are "Dev" for development mode or "Prod" for production mode. This determines whether the `Disable-LocalUserAccounts` function will skip certain accounts.
   
    .EXAMPLE
    $params = @{
        TempUser = "TempUser"
        RegistrySettings = @{
            "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" = @{
                "dontdisplaylastusername" = @{
                    "Type" = "DWORD"
                    "Data" = "0"
                }
                "legalnoticecaption" = @{
                    "Type" = "String"
                    "Data" = $null
                }
                "legalnoticetext" = @{
                    "Type" = "String"
                    "Data" = $null
                }
            }
            "HKLM:\Software\Policies\Microsoft\Windows\Personalization" = @{
                "NoLockScreen" = @{
                    "Type" = "DWORD"
                    "Data" = "0"
                }
            }
        }
        MigrationDirectories = @(
            "C:\ProgramData\AADMigration\Files",
            "C:\ProgramData\AADMigration\Scripts",
            "C:\ProgramData\AADMigration\Toolkit"
        )
        Mode = "Dev"
    }
    Execute-MigrationCleanupTasks @params
    Executes the post-run operations in Dev mode.
    #>

  
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TempUser,
  
        [Parameter(Mandatory = $true)]
        $RegistrySettings,
  
        [Parameter(Mandatory = $true)]
        [string[]]$MigrationDirectories,

        [Parameter(Mandatory = $true)]
        [ValidateSet("Dev", "Prod")]
        [string]$Mode
    )
  
    Begin {
        Write-EnhancedLog -Message "Starting Execute-MigrationCleanupTasks function in $Mode mode" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
  
    Process {
        try {
            # Remove temporary user account
            Write-EnhancedLog -Message "Removing temporary user account: $TempUser" -Level "INFO"
            $removeUserParams = @{
                UserName = $TempUser
            }
            Remove-LocalUserAccount @removeUserParams
            Write-EnhancedLog -Message "Temporary user account $TempUser removed" -Level "INFO"
  
            Manage-LocalUserAccounts -Mode $Mode
  
            # Set registry values
            # Write-EnhancedLog -Message "Applying registry settings" -Level "INFO"
            # foreach ($regPath in $RegistrySettings.Keys) {
            # foreach ($regName in $RegistrySettings[$regPath].Keys) {
            # $regSetting = $RegistrySettings[$regPath][$regName]

            # if ($null -ne $regSetting["Data"]) {
            # Write-EnhancedLog -Message "Setting registry value $regName at $regPath" -Level "INFO"
                    
            # $regParams = @{
            # RegKeyPath = $regPath
            # RegValName = $regName
            # RegValType = $regSetting["Type"]
            # RegValData = $regSetting["Data"]
            # }
                    
            # # If the data is an empty string, explicitly set it as such
            # if ($regSetting["Data"] -eq "") {
            # $regParams.RegValData = ""
            # }
                    
            # Set-RegistryValue @regParams
            # Write-EnhancedLog -Message "Registry value $regName at $regPath set" -Level "INFO"
            # }
            # else {
            # Write-EnhancedLog -Message "Skipping registry value $regName at $regPath due to null data" -Level "WARNING"
            # }
                    
            # }
            # }


            # Apply the registry settings using the defined hash table
            # Apply-RegistrySettings -RegistrySettings $RegistrySettings

            # Iterate through each registry setting
            # foreach ($regSetting in $RegistrySettings) {
            # $regKeyPath = $regSetting.RegKeyPath
    
            # # Apply the registry setting
            # Apply-RegistrySettings -RegistrySettings @($regSetting) -RegKeyPath $regKeyPath
            # }
            



            # Create a new hashtable to store settings grouped by their RegKeyPath
            $groupedSettings = @{}

            # Group the registry settings by their RegKeyPath
            foreach ($regSetting in $RegistrySettings) {
                $regKeyPath = $regSetting.RegKeyPath
     
                if (-not $groupedSettings.ContainsKey($regKeyPath)) {
                    $groupedSettings[$regKeyPath] = @()
                }
     
                # Add the current setting to the appropriate group
                $groupedSettings[$regKeyPath] += $regSetting
            }
     
            # Now apply the grouped registry settings
            foreach ($regKeyPath in $groupedSettings.Keys) {
                $settingsForKey = $groupedSettings[$regKeyPath]
     
                # Call Apply-RegistrySettings once per group with the correct RegKeyPath
                Apply-RegistrySettings -RegistrySettings $settingsForKey -RegKeyPath $regKeyPath
            }



            # #Region Set registry values

            # # Initialize counters and summary table
            # $infoCount = 0
            # $warningCount = 0
            # $errorCount = 0
            # # Initialize the summary table using a .NET List for better performance
            # $summaryTable = [System.Collections.Generic.List[PSCustomObject]]::new()

            # # Set registry values
            # Write-EnhancedLog -Message "Applying registry settings" -Level "INFO"
            # foreach ($regPath in $RegistrySettings.Keys) {
            # foreach ($regName in $RegistrySettings[$regPath].Keys) {
            # $regSetting = $RegistrySettings[$regPath][$regName]

            # $summaryRow = [PSCustomObject]@{
            # RegistryPath = $regPath
            # RegistryName = $regName
            # RegistryValue = if ($null -ne $regSetting["Data"]) { $regSetting["Data"] } else { "null" }
            # Status = ""
            # }

            # if ($null -ne $regSetting["Data"]) {
            # Write-EnhancedLog -Message "Setting registry value $regName at $regPath" -Level "INFO"
            # $infoCount++

            # $regParams = @{
            # RegKeyPath = $regPath
            # RegValName = $regName
            # RegValType = $regSetting["Type"]
            # RegValData = $regSetting["Data"]
            # }

            # # If the data is an empty string, explicitly set it as such
            # if ($regSetting["Data"] -eq "") {
            # $regParams.RegValData = ""
            # }

            # try {
            # # Set-RegistryValue @regParams

            # # Call the Set-RegistryValue function and capture the result
            # $setRegistryResult = Set-RegistryValue @regParams

            # # Build decision-making logic based on the result
            # if ($setRegistryResult -eq $true) {
            # Write-EnhancedLog -Message "Successfully set the registry value: $regValName at $regKeyPath" -Level "INFO"
            # $summaryRow.Status = "Success"
            # }
            # else {
            # Write-EnhancedLog -Message "Failed to set the registry value: $regValName at $regKeyPath" -Level "ERROR"
            # $summaryRow.Status = "Failed"
            # }

            # Write-EnhancedLog -Message "Registry value $regName at $regPath set" -Level "INFO"
                            
            # }
            # catch {
            # Write-EnhancedLog -Message "Error setting registry value $regName at $regPath $($_.Exception.Message)" -Level "ERROR"
            # $errorCount++
            # $summaryRow.Status = "Failed"
            # }
            # }
            # else {
            # Write-EnhancedLog -Message "Skipping registry value $regName at $regPath due to null data" -Level "WARNING"
            # $warningCount++
            # $summaryRow.Status = "Skipped"
            # }

            # $summaryTable.Add($summaryRow)
            # }
            # }

            # # Final Summary Report
            # Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"
            # Write-EnhancedLog -Message "Final Summary Report" -Level "NOTICE"
            # Write-EnhancedLog -Message "Total registry settings processed: $($infoCount + $warningCount + $errorCount)" -Level "INFO"
            # Write-EnhancedLog -Message "Successfully applied registry settings: $infoCount" -Level "INFO"
            # Write-EnhancedLog -Message "Skipped registry settings (due to null data): $warningCount" -Level "WARNING"
            # Write-EnhancedLog -Message "Failed registry settings: $errorCount" -Level "ERROR"
            # Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"

            # # Color-coded summary for the console
            # Write-Host "----------------------------------------" -ForegroundColor White
            # Write-Host "Final Summary Report" -ForegroundColor Cyan
            # Write-Host "Total registry settings processed: $($infoCount + $warningCount + $errorCount)" -ForegroundColor White
            # Write-Host "Successfully applied registry settings: $infoCount" -ForegroundColor Green
            # Write-Host "Skipped registry settings (due to null data): $warningCount" -ForegroundColor Yellow
            # Write-Host "Failed registry settings: $errorCount" -ForegroundColor Red
            # Write-Host "----------------------------------------" -ForegroundColor White

            # # Display the summary table of registry keys and their final states
            # Write-Host "Registry Settings Summary:" -ForegroundColor Cyan
            # $summaryTable | Format-Table -AutoSize

            # # Optionally log the summary to the enhanced log as well
            # foreach ($row in $summaryTable) {
            # Write-EnhancedLog -Message "RegistryPath: $($row.RegistryPath), RegistryName: $($row.RegistryName), Value: $($row.RegistryValue), Status: $($row.Status)" -Level "INFO"
            # }


            # #endRegion Set registry values
  


            
  
            # Remove scheduled tasks
            Write-EnhancedLog -Message "Removing scheduled tasks in TaskPath: AAD Migration" -Level "INFO"

            # Retrieve all tasks in the "AAD Migration" path
            $tasks = Get-ScheduledTask -TaskPath "\AAD Migration\" -ErrorAction SilentlyContinue

            # Loop through each task and unregister it using the Unregister-ScheduledTaskWithLogging function
            foreach ($task in $tasks) {
                Unregister-ScheduledTaskWithLogging -TaskName $task.TaskName
            }

            Write-EnhancedLog -Message "Scheduled tasks removed from TaskPath: AAD Migration" -Level "INFO"
  
        
  
            # Clear OneDrive cache
            Write-EnhancedLog -Message "Clearing OneDrive cache" -Level "INFO"
            # Clear-OneDriveCache

            $CreateOneDriveCacheClearTaskParams = @{
                TaskPath               = "AAD Migration"
                TaskName               = "Clear OneDrive Cache"
                ScriptDirectory        = "C:\ProgramData\AADMigration\Scripts"
                ScriptName             = "ClearOneDriveCache.Task.ps1"
                TaskArguments          = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
                TaskRepetitionDuration = "P1D"
                TaskRepetitionInterval = "PT30M"
                TaskPrincipalGroupId   = "BUILTIN\Users"
                PowerShellPath         = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
                TaskDescription        = "Clears the OneDrive cache by restarting the OneDrive process"
                AtLogOn                = $true
            }

            Create-OneDriveCacheClearTask @CreateOneDriveCacheClearTaskParams


            $taskParams = @{
                TaskPath = "AAD Migration"
                TaskName = "Clear OneDrive Cache"
            }

            # Trigger OneDrive Sync Status Scheduled Task
            Trigger-ScheduledTask @taskParams


            # Remove migration files
            Write-EnhancedLog -Message "Removing migration directories: $MigrationDirectories" -Level "INFO"
            $removeFilesParams = @{
                Directories = $MigrationDirectories
            }
            Remove-MigrationFiles @removeFilesParams
            Write-EnhancedLog -Message "Migration directories removed: $MigrationDirectories" -Level "INFO"




            Remove-AADMigrationArtifacts



            Write-EnhancedLog -Message "OneDrive cache cleared" -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Execute-MigrationCleanupTasks function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
  
    End {
        Write-EnhancedLog -Message "Exiting Execute-MigrationCleanupTasks function" -Level "Notice"
    }
}
#EndRegion '.\Public\Execute-MigrationCleanupTasks.ps1' 341
#Region '.\Public\Execute-MigrationToolkit.ps1' -1

function Execute-MigrationToolkit {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ServiceUI,

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

    Begin {
        Write-EnhancedLog -Message "Starting Execute-MigrationToolkit function" -Level "INFO"
        Log-Params -Params @{
            ServiceUI = $ServiceUI
            ExePath   = $ExePath
        }
    }

    Process {
        try {
            $targetProcesses = @(Get-WmiObject -Query "Select * FROM Win32_Process WHERE Name='explorer.exe'" -ErrorAction SilentlyContinue)
            if ($targetProcesses.Count -eq 0) {
                Write-EnhancedLog -Message "No user logged in, running without ServiceUI" -Level "INFO"
                Start-Process -FilePath $ExePath -ArgumentList '-DeployMode "NonInteractive"' -Wait -NoNewWindow
            } else {
                foreach ($targetProcess in $targetProcesses) {
                    $Username = $targetProcess.GetOwner().User
                    Write-EnhancedLog -Message "$Username logged in, running with ServiceUI" -Level "INFO"
                }
                Start-Process -FilePath $ServiceUI -ArgumentList "-Process:explorer.exe $ExePath" -NoNewWindow
            }
        } catch {
            $ErrorMessage = $_.Exception.Message
            Write-EnhancedLog -Message "An error occurred: $ErrorMessage" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Install Exit Code = $LASTEXITCODE" -Level "INFO"
        Write-EnhancedLog -Message "Exiting Execute-MigrationToolkit function" -Level "INFO"
        Exit $LASTEXITCODE
    }
}

# # Define paths
# $ToolkitPaths = @{
# ServiceUI = "C:\ProgramData\AADMigration\Files\ServiceUI.exe"
# ExePath = "C:\ProgramData\AADMigration\Toolkit\Deploy-Application.exe"
# }

# # Example usage with splatting
# Execute-MigrationToolkit @ToolkitPaths
#EndRegion '.\Public\Execute-MigrationToolkit.ps1' 54
#Region '.\Public\Find-NewStatusFile.ps1' -1

function Find-NewStatusFile {
    <#
    .SYNOPSIS
    Finds the status file in user profiles or system context with retries.
 
    .DESCRIPTION
    This function searches for a specified status file in user profiles or system context (depending on whether the script is running as SYSTEM).
    It will retry the search up to a defined number of times with a specified interval between retries if the file is not found on the first attempt.
 
    .PARAMETER LogFolder
    The folder name within the user profile or system context where the log file is expected to be found.
 
    .PARAMETER StatusFileName
    The name of the status file to be located.
 
    .PARAMETER MaxRetries
    The maximum number of retry attempts to find the status file.
 
    .PARAMETER RetryInterval
    The time (in seconds) to wait between retry attempts.
 
    .EXAMPLE
    Find-NewStatusFile -LogFolder "logs" -StatusFileName "ODSyncUtilStatus.json" -MaxRetries 5 -RetryInterval 10
 
    This command will search for the status file "ODSyncUtilStatus.json" in the "logs" folder, retrying up to 5 times with a 10-second interval between retries.
 
    .NOTES
    Author: Abdullah Ollivierre
    Date: 2024-09-06
 
    .LINK
    https://github.com/yourproject/documentation
 
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = "Specify the log folder path.")]
        [string]$LogFolder,

        [Parameter(Mandatory = $true,
            HelpMessage = "Specify the status file name.")]
        [string]$StatusFileName,

        [Parameter(Mandatory = $true,
            HelpMessage = "Specify the maximum number of retries.")]
        [ValidateRange(1, 100)]
        [int]$MaxRetries,

        [Parameter(Mandatory = $true,
            HelpMessage = "Specify the interval (in seconds) between retries.")]
        [ValidateRange(1, 60)]
        [int]$RetryInterval
    )

    Begin {
        Write-EnhancedLog -Message "Starting Find-NewStatusFile function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
        $isSystem = Test-RunningAsSystem
        $fileFound = $false
        $statusFile = $null
    }

    Process {
        $retryCount = 0

        try {
            while ($retryCount -lt $MaxRetries -and -not $fileFound) {
                Write-EnhancedLog -Message "Attempt $($retryCount + 1) of $MaxRetries to find status file" -Level "INFO"

                if ($isSystem) {
                    $userProfiles = Get-ChildItem 'C:\Users' -Directory | Where-Object { $_.Name -notlike "Public" -and $_.Name -notlike "Default*" }
                    foreach ($profile in $userProfiles) {
                        $profileLogFolder = Join-Path -Path $profile.FullName -ChildPath $LogFolder
                        $profileStatusFile = Join-Path -Path $profileLogFolder -ChildPath $StatusFileName
                        Write-EnhancedLog -Message "Checking status file in profile: $($profile.FullName)" -Level "INFO"
                        if (Test-Path -Path $profileStatusFile) {
                            $fileFound = $true
                            $statusFile = Get-Item -Path $profileStatusFile
                            Write-EnhancedLog -Message "Status file found in $($statusFile.FullName)" -Level "INFO"
                            break
                        }
                    }
                }
                else {
                    $logFolder = Join-Path -Path $env:USERPROFILE -ChildPath $LogFolder
                    $statusFile = Join-Path -Path $logFolder -ChildPath $StatusFileName
                    Write-EnhancedLog -Message "Checking status file in current user's profile: $logFolder" -Level "INFO"

                    if (Test-Path -Path $statusFile) {
                        $fileFound = $true
                        Write-EnhancedLog -Message "Status file found: $statusFile" -Level "INFO"
                    }
                }

                if (-not $fileFound) {
                    Write-EnhancedLog -Message "Status file not found. Retrying in $RetryInterval seconds..." -Level "WARNING"
                    Start-Sleep -Seconds $RetryInterval
                    $retryCount++
                }
            }

            if (-not $fileFound) {
                $errorMessage = "Status file not found after $MaxRetries retries."
                Write-EnhancedLog -Message $errorMessage -Level "ERROR"
                Write-EnhancedLog -Message "Please check if you are logged in to OneDrive and try again." -Level "ERROR"
                throw [System.Exception]::new($errorMessage)
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Find-NewStatusFile: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_  # Rethrow the error to halt the script if necessary
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Find-NewStatusFile function" -Level "NOTICE"
        return $statusFile
    }
}
#EndRegion '.\Public\Find-NewStatusFile.ps1' 123
#Region '.\Public\Find-OneDrivePath.ps1' -1

function Find-OneDrivePath {
    <#
    .SYNOPSIS
    Finds the path to the OneDrive executable in common installation directories.
   
    .DESCRIPTION
    The Find-OneDrivePath function searches for the OneDrive executable in various common installation directories and returns the path if found.
   
    .EXAMPLE
    $oneDrivePath = Find-OneDrivePath
    #>


    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Find-OneDrivePath function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            $possiblePaths = @(
                "C:\Program Files\Microsoft OneDrive\OneDrive.exe",
                "C:\Program Files (x86)\Microsoft OneDrive\OneDrive.exe",
                "$env:LOCALAPPDATA\Microsoft\OneDrive\OneDrive.exe",
                "$env:LOCALAPPDATA\Microsoft\OneDrive\Update\OneDriveSetup.exe",
                "C:\Users\$env:USERNAME\AppData\Local\Microsoft\OneDrive\OneDrive.exe"
            )

            foreach ($path in $possiblePaths) {
                if (Test-Path -Path $path) {
                    Write-EnhancedLog -Message "Found OneDrive at: $path" -Level "INFO"
                    return $path
                }
            }

            Write-EnhancedLog -Message "OneDrive executable not found in common directories." -Level "WARNING"
            return $null
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Find-OneDrivePath function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Find-OneDrivePath function" -Level "Notice"
    }
}

# Example usage
# $oneDrivePath = Find-OneDrivePath
#EndRegion '.\Public\Find-OneDrivePath.ps1' 55
#Region '.\Public\Generate-RemoveIntuneMgmtSummaryReport.ps1' -1



# Function to generate a summary report
function Generate-RemoveIntuneMgmtSummaryReport {
    param (
        [int]$successCount,
        [int]$warningCount,
        [int]$errorCount,
        [System.Collections.Generic.List[PSCustomObject]]$summaryTable
    )

    # Final Summary Report
    Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"
    Write-EnhancedLog -Message "Final Intune Management Cleanup Summary Report" -Level "NOTICE"
    Write-EnhancedLog -Message "Total operations processed: $($successCount + $warningCount + $errorCount)" -Level "INFO"
    Write-EnhancedLog -Message "Successfully completed: $successCount" -Level "INFO"
    Write-EnhancedLog -Message "Warnings: $warningCount" -Level "WARNING"
    Write-EnhancedLog -Message "Errors: $errorCount" -Level "ERROR"
    Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"

    # Color-coded summary for the console
    Write-Host "----------------------------------------" -ForegroundColor White
    Write-Host "Final Intune Management Cleanup Summary Report" -ForegroundColor Cyan
    Write-Host "Total operations processed: $($successCount + $warningCount + $errorCount)" -ForegroundColor White
    Write-Host "Successfully completed: $successCount" -ForegroundColor Green
    Write-Host "Warnings: $warningCount" -ForegroundColor Yellow
    Write-Host "Errors: $errorCount" -ForegroundColor Red
    Write-Host "----------------------------------------" -ForegroundColor White

    # Display the summary table of actions and their final states
    Write-Host "Intune Management Cleanup Summary:" -ForegroundColor Cyan
    $summaryTable | Format-Table -AutoSize

    # Optionally log the summary to the enhanced log as well
    foreach ($row in $summaryTable) {
        Write-EnhancedLog -Message "Action: $($row.Action), Path: $($row.Path), Status: $($row.Status)" -Level "INFO"
    }
}
#EndRegion '.\Public\Generate-RemoveIntuneMgmtSummaryReport.ps1' 39
#Region '.\Public\Get-AllGroupAccounts-Archive.ps1' -1

# function Get-AllGroupAccounts {
# param (
# [string]$GroupName = "Administrators"
# )

# Begin {
# Write-EnhancedLog -Message "Starting Get-AllGroupAccounts function for group '$GroupName'" -Level "Notice"
# # Initialize a list to store all accounts
# $allAccounts = [System.Collections.Generic.List[PSCustomObject]]::new()
# }

# Process {
# # Get all group members
# $admins = Get-GroupMembers -GroupName $GroupName

# if ($admins.Count -gt 0) {
# foreach ($admin in $admins) {
# # Extract the account name from the PartComponent
# $accountName = Extract-AccountName -PartComponent $admin.PartComponent

# if ($accountName) {
# # Try to resolve the account to check if it's orphaned
# $resolved = Resolve-Account -AccountName $accountName

# # Add the account to the list, flagging if it is orphaned or not
# $allAccounts.Add([pscustomobject]@{
# AccountName = $accountName
# SID = $admin.PartComponent
# IsOrphaned = -not $resolved
# })
                    
# # Log the status of the account
# if ($resolved) {
# Write-EnhancedLog -Message "Resolved account: $accountName" -Level "INFO"
# } else {
# Write-EnhancedLog -Message "Orphaned account detected: $accountName" -Level "WARNING"
# }
# }
# }
# }
# else {
# Write-EnhancedLog -Message "No members found in the '$GroupName' group." -Level "WARNING"
# }
# }

# End {
# if ($allAccounts.Count -eq 0) {
# Write-EnhancedLog -Message "No accounts found in the '$GroupName' group." -Level "INFO"
# }
# else {
# Write-EnhancedLog -Message "All accounts retrieved from '$GroupName':" -Level "INFO"

# # Output the accounts to the log properly formatted as a string
# $accountsSummary = $allAccounts | Out-String
# Write-EnhancedLog -Message $accountsSummary -Level "INFO"
# }

# Write-EnhancedLog -Message "Exiting Get-AllGroupAccounts function" -Level "Notice"
# return $allAccounts
# }
# }
#EndRegion '.\Public\Get-AllGroupAccounts-Archive.ps1' 62
#Region '.\Public\Get-DSRegStatus.ps1' -1


function Get-DSRegStatus {
    <#
    .SYNOPSIS
    Checks the device's join status (Workgroup, Azure AD Joined, Hybrid Joined, or On-prem Joined) and MDM enrollment (Intune Enrolled or Not).
 
    .DESCRIPTION
    The Get-DSRegStatus function runs the dsregcmd /status command and parses its output to determine the device's join status and whether it is enrolled in Microsoft Intune.
 
    .NOTES
    Version: 1.3
    Author: Abdullah Ollivierre
    Creation Date: 2024-08-15
    #>


    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Get-DSRegStatus function" -Level "Notice"
    }

    Process {
        try {
            # Execute dsregcmd /status
            Write-EnhancedLog -Message "Running dsregcmd /status" -Level "INFO"
            $dsregcmdOutput = dsregcmd /status

            # Parse dsregcmd output to determine join status
            Write-EnhancedLog -Message "Parsing dsregcmd output" -Level "INFO"

            $isAzureADJoined = $dsregcmdOutput -match '.*AzureAdJoined\s*:\s*YES'
            $isHybridJoined = $dsregcmdOutput -match '.*DomainJoined\s*:\s*YES' -and $isAzureADJoined
            $isOnPremJoined = $dsregcmdOutput -match '.*DomainJoined\s*:\s*YES' -and -not $isAzureADJoined
            $isWorkgroup = -not ($isAzureADJoined -or $isHybridJoined -or $isOnPremJoined)

            # Determine MDM enrollment status
            $isMDMEnrolled = $dsregcmdOutput -match '.*MDMUrl\s*:\s*(https://manage\.microsoft\.com|https://enrollment\.manage\.microsoft\.com)'

            # Log the parsed results
            Write-EnhancedLog -Message "Join status parsed: Workgroup: $isWorkgroup, AzureAD: $isAzureADJoined, Hybrid: $isHybridJoined, OnPrem: $isOnPremJoined" -Level "INFO"
            Write-EnhancedLog -Message "MDM enrollment status: $isMDMEnrolled" -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "Error while parsing dsregcmd output: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Returning parsed device status" -Level "INFO"
        return @{
            IsWorkgroup     = $isWorkgroup
            IsAzureADJoined = $isAzureADJoined
            IsHybridJoined  = $isHybridJoined
            IsOnPremJoined  = $isOnPremJoined
            IsMDMEnrolled   = $isMDMEnrolled
        }
    }
}


#Here is an example for a decision-making tree

# # Main script execution block
# $dsregStatus = Get-DSRegStatus

# # Determine and output the join status
# if ($dsregStatus.IsWorkgroup) {
# Write-Output "Device is Workgroup joined (not Azure AD, Hybrid, or On-prem Joined)."
# } elseif ($dsregStatus.IsAzureADJoined -and -not $dsregStatus.IsHybridJoined) {
# Write-Output "Device is Azure AD Joined."
# } elseif ($dsregStatus.IsHybridJoined) {
# Write-Output "Device is Hybrid Joined (both On-prem and Azure AD Joined)."
# } elseif ($dsregStatus.IsOnPremJoined) {
# Write-Output "Device is On-prem Joined only."
# }

# # Determine and output the MDM enrollment status
# if ($dsregStatus.IsMDMEnrolled) {
# Write-Output "Device is Intune Enrolled."
# } else {
# Write-Output "Device is NOT Intune Enrolled."
# }

# # Exit code based on Azure AD and MDM status
# if ($dsregStatus.IsAzureADJoined -and -not $dsregStatus.IsHybridJoined -and $dsregStatus.IsMDMEnrolled) {
# Write-Output "Device is Azure AD Joined and Intune Enrolled. No migration needed."
# exit 0 # Do not migrate: Device is Azure AD Joined and Intune Enrolled
# } else {
# # Migrate: All other cases where the device is not 100% Azure AD joined or is hybrid/on-prem joined
# exit 1
# }
#EndRegion '.\Public\Get-DSRegStatus.ps1' 95
#Region '.\Public\Get-EnhancedLocalGroupMembers.ps1' -1

function Get-EnhancedLocalGroupMembers {
    <#
    .SYNOPSIS
    Retrieves and logs members of a specified local group, differentiating between user and system accounts.
 
    .DESCRIPTION
    The Get-EnhancedLocalGroupMembers function retrieves all members of a specified local group. It logs the retrieval process, handles errors gracefully, and differentiates between user, system accounts, and built-in groups. It also provides a summary report with success and failure counts.
 
    .PARAMETER GroupName
    The name of the local group to retrieve members from (default is "Administrators").
 
    .PARAMETER PassThru
    Allows pipeline support and passes the objects to the pipeline for further processing.
 
    .EXAMPLE
    Get-EnhancedLocalGroupMembers -GroupName "Administrators" | Format-Table
    Retrieves members of the "Administrators" group and formats the output as a table.
 
    .EXAMPLE
    "Administrators", "Users" | Get-EnhancedLocalGroupMembers
    Retrieves members from multiple groups using pipeline input.
    #>


    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]
    param (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Name of the local group to retrieve members from.")]
        [ValidateNotNullOrEmpty()]
        [string]$GroupName = "Administrators",

        [Parameter(Mandatory = $false, HelpMessage = "Passes objects down the pipeline.")]
        [switch]$PassThru
    )

    Begin {
        Write-EnhancedLog -Message "Starting Get-EnhancedLocalGroupMembers function for group '$GroupName'" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Initialize a System.Collections.Generic.List object to store group members efficiently
        $groupMembers = [System.Collections.Generic.List[PSCustomObject]]::new()
        $successCount = 0
        $failureCount = 0
    }

    Process {
        if ($PSCmdlet.ShouldProcess("Group '$GroupName'", "Retrieve group members")) {
            try {
                $group = [ADSI]"WinNT://./$GroupName,group"
                if (!$group) {
                    Write-EnhancedLog -Message "Group '$GroupName' not found." -Level "ERROR"
                    throw "Group '$GroupName' not found."
                }
                Write-EnhancedLog -Message "Group '$GroupName' found. Retrieving members..." -Level "INFO"

                $members = $group.psbase.Invoke("Members")
                foreach ($member in $members) {
                    try {
                        # Get the full name of the account
                        $accountName = $member.GetType().InvokeMember("Name", 'GetProperty', $null, $member, $null)

                        # Determine if it's a user or system account
                        $accountType = if ($accountName -match "^NT AUTHORITY") {
                            "System Account"
                        } elseif ($accountName -match "^BUILTIN") {
                            "Built-in Group"
                        } else {
                            "User Account"
                        }

                        # Create a custom object for each account and add it to the list efficiently
                        $groupMember = [PSCustomObject]@{
                            Account = $accountName
                            Type    = $accountType
                        }

                        $groupMembers.Add($groupMember)
                        $successCount++
                        
                        # Log the account details
                        Write-EnhancedLog -Message "Account: $accountName, Type: $accountType" -Level "INFO"

                        # Output the object to the pipeline if PassThru is enabled
                        if ($PassThru) {
                            $groupMember
                        }
                    }
                    catch {
                        Write-EnhancedLog -Message "Failed to process member: $($_.Exception.Message)" -Level "WARNING"
                        $failureCount++
                    }
                }
            }
            catch {
                Write-EnhancedLog -Message "Error retrieving group members for group '$GroupName': $($_.Exception.Message)" -Level "ERROR"
                Handle-Error -ErrorRecord $_
                throw
            }
        } else {
            Write-EnhancedLog -Message "Operation skipped due to WhatIf or Confirm." -Level "INFO"
        }
    }

    End {
        Write-EnhancedLog -Message "Finalizing the member retrieval process for group '$GroupName'" -Level "INFO"
        if ($groupMembers.Count -gt 0) {
            Write-EnhancedLog -Message "Successfully retrieved members for group '$GroupName'." -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "No members found for group '$GroupName'." -Level "WARNING"
        }

        # Output the summary report
        Show-SummaryReport -SuccessCount $successCount -FailureCount $failureCount

        # Output the list of group members only if PassThru is not enabled
        if (-not $PassThru) {
            $groupMembers
        }
    }
}

# Summary Report Function
function Show-SummaryReport {
    param (
        [int]$SuccessCount,
        [int]$FailureCount
    )

    $totalCount = $SuccessCount + $FailureCount

    # Output the summary with color coding
    Write-Host "Summary Report" -ForegroundColor Cyan
    Write-Host "Total Members Processed: $totalCount" -ForegroundColor Yellow
    Write-Host "Success: $SuccessCount" -ForegroundColor Green
    Write-Host "Failures: $FailureCount" -ForegroundColor Red
}

# Example usage:
# $params = @{
# GroupName = "Administrators"
# }
# Get-EnhancedLocalGroupMembers @params

# Example pipeline usage:
# "Administrators", "Users" | Get-EnhancedLocalGroupMembers -PassThru




























# function Add-UserToLocalAdminGroup {
# [CmdletBinding(SupportsShouldProcess = $true)]
# param (
# [Parameter(Mandatory = $true, HelpMessage = "Specify the username to add to the local admin group.")]
# [string]$Username
# )

# Process {
# # Use ADSI to add the user to the local administrators group
# if ($PSCmdlet.ShouldProcess("Administrators group", "Adding user '$Username'")) {
# try {
# $adminGroup = [ADSI]"WinNT://./Administrators,group"
# $user = [ADSI]"WinNT://./$Username,user"
# $adminGroup.Add($user.PSBase.Path)
# Write-Host "Successfully added $Username to the local Administrators group." -ForegroundColor Green
# }
# catch {
# Write-Host "Failed to add user: $($_.Exception.Message)" -ForegroundColor Red
# }
# }
# }
# }


# function Verify-UserMembershipBefore {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true, HelpMessage = "Specify the username to verify.")]
# [string]$Username
# )

# Process {
# Write-Host "Verifying if '$Username' is a member of the local Administrators group before operation..." -ForegroundColor Yellow

# # Get the list of current members
# $admins = Get-EnhancedLocalGroupMembers -GroupName "Administrators"
# $userMembership = $admins | Where-Object { $_.Account -eq $Username }

# if ($userMembership) {
# Write-Host "User '$Username' is already a member of the local Administrators group." -ForegroundColor Cyan
# return $true
# }
# else {
# Write-Host "User '$Username' is NOT a member of the local Administrators group." -ForegroundColor Red
# return $false
# }
# }
# }




# function Verify-UserMembershipAfter {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true, HelpMessage = "Specify the username to verify.")]
# [string]$Username
# )

# Process {
# Write-Host "Verifying if '$Username' has been successfully added to the local Administrators group..." -ForegroundColor Yellow

# # Get the list of current members
# $admins = Get-EnhancedLocalGroupMembers -GroupName "Administrators"
# $userMembership = $admins | Where-Object { $_.Account -eq $Username }

# if ($userMembership) {
# Write-Host "User '$Username' has been successfully added to the local Administrators group." -ForegroundColor Green
# return $true
# }
# else {
# Write-Host "User '$Username' was NOT added to the local Administrators group." -ForegroundColor Red
# return $false
# }
# }
# }





# function Verify-GroupMemberships {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true, HelpMessage = "Specify the username to verify.")]
# [string]$Username
# )

# Process {
# Write-Host "Verifying group memberships for user '$Username'..." -ForegroundColor Yellow

# try {
# $user = [ADSI]"WinNT://./$Username,user"
# $groups = $user.Invoke("Groups")

# foreach ($group in $groups) {
# $groupName = $group.GetType().InvokeMember("Name", 'GetProperty', $null, $group, $null)
# Write-Host "$Username is a member of group: $groupName" -ForegroundColor Cyan
# }
# }
# catch {
# Write-Host "Failed to retrieve group memberships for '$Username': $($_.Exception.Message)" -ForegroundColor Red
# }
# }
# }





# # Verifying before
# $beforeMember = Verify-UserMembershipBefore -Username "Admin-Abdullah"
# Verify-GroupMemberships -Username "Admin-Abdullah"

# # Add user if not already a member
# if (-not $beforeMember) {
# Add-UserToLocalAdminGroup -Username "Admin-Abdullah"
# }

# # Verifying after
# Verify-UserMembershipAfter -Username "Admin-Abdullah"
# Verify-GroupMemberships -Username "Admin-Abdullah"






#EndRegion '.\Public\Get-EnhancedLocalGroupMembers.ps1' 301
#Region '.\Public\Get-GitHubPAT.ps1' -1

function Get-GitHubPAT {
    [CmdletBinding()]
    param ()

    # Load necessary assemblies for Windows Forms
    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing

    # Create a new form
    $form = New-Object system.windows.forms.Form
    $form.Text = "GitHub PAT Input"
    $form.Size = New-Object System.Drawing.Size(350,150)
    $form.StartPosition = "CenterScreen"

    # Create a label for instructions
    $label = New-Object system.windows.forms.Label
    $label.Text = "Enter your GitHub Personal Access Token (PAT):"
    $label.AutoSize = $true
    $label.Location = New-Object System.Drawing.Point(10,10)
    $form.Controls.Add($label)

    # Create a textbox for the PAT input, with masking (password char)
    $textbox = New-Object system.windows.forms.TextBox
    $textbox.Size = New-Object System.Drawing.Size(300,20)
    $textbox.Location = New-Object System.Drawing.Point(10,40)
    $textbox.UseSystemPasswordChar = $true # Mask input
    $form.Controls.Add($textbox)

    # Create an OK button
    $okButton = New-Object system.windows.forms.Button
    $okButton.Text = "OK"
    $okButton.Location = New-Object System.Drawing.Point(190,70)
    $okButton.Add_Click({
        $form.DialogResult = [System.Windows.Forms.DialogResult]::OK
        $form.Close()
    })
    $form.Controls.Add($okButton)

    # Create a Cancel button
    $cancelButton = New-Object system.windows.forms.Button
    $cancelButton.Text = "Cancel"
    $cancelButton.Location = New-Object System.Drawing.Point(100,70)
    $cancelButton.Add_Click({
        $form.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
        $form.Close()
    })
    $form.Controls.Add($cancelButton)

    # Show the form
    $form.Topmost = $true
    $result = $form.ShowDialog()

    # Handle the result
    if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
        # Convert the text input to a SecureString
        $SecurePAT = ConvertTo-SecureString $textbox.Text -AsPlainText -Force
        Write-Host "PAT securely captured."
        return $SecurePAT
    } else {
        Write-Host "Operation canceled."
        return $null
    }
}
#EndRegion '.\Public\Get-GitHubPAT.ps1' 64
#Region '.\Public\Get-KeyProtectorId.ps1' -1

function Get-KeyProtectorId {
    <#
    .SYNOPSIS
    Retrieves the key protector ID for the specified drive.
 
    .DESCRIPTION
    The Get-KeyProtectorId function retrieves the key protector ID for the specified BitLocker protected drive.
 
    .PARAMETER BitlockerDrive
    The drive letter of the BitLocker protected drive.
 
    .EXAMPLE
    Get-KeyProtectorId -BitlockerDrive "C:"
    Retrieves the key protector ID for drive C:.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$BitlockerDrive
    )

    Begin {
        Write-EnhancedLog -Message "Starting Get-KeyProtectorId function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            $bitlockerVolume = Get-BitLockerVolume -MountPoint $BitlockerDrive
            $keyProtector = $bitlockerVolume.KeyProtector | Where-Object { $_.KeyProtectorType -eq 'RecoveryPassword' }
            Write-EnhancedLog -Message "Retrieved key protector ID for drive: $BitlockerDrive" -Level "INFO"
            return $keyProtector.KeyProtectorId
        }
        catch {
            Write-EnhancedLog -Message "Failed to retrieve key protector ID for drive: $BitlockerDrive" -Level "ERROR"
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Get-KeyProtectorId function" -Level "Notice"
    }
}
#EndRegion '.\Public\Get-KeyProtectorId.ps1' 45
#Region '.\Public\Get-OrphanedSIDs-Archive.ps1' -1

# # function Get-OrphanedSIDs {
# # <#
# # .SYNOPSIS
# # Identifies orphaned SIDs in the 'Administrators' group.

# # .DESCRIPTION
# # The Get-OrphanedSIDs function retrieves members of the 'Administrators' group and checks if each member resolves to a valid user or group. Orphaned SIDs are identified when the account cannot be resolved, and those SIDs are returned as the output.

# # .EXAMPLE
# # $orphanedSIDs = Get-OrphanedSIDs
# # Outputs a list of orphaned SIDs from the 'Administrators' group.
# # #>

# # [CmdletBinding()]
# # param (
# # [string]$GroupName = "Administrators"
# # )

# # Begin {
# # Write-EnhancedLog -Message "Starting Get-OrphanedSIDs function" -Level "Notice"
        
# # # Initialize a list for storing orphaned SIDs efficiently
# # $orphanedSIDs = [System.Collections.Generic.List[PSCustomObject]]::new()

# # Write-EnhancedLog -Message "Retrieving members of the '$GroupName' group." -Level "INFO"
# # }

# # Process {
# # # Use WMI to retrieve members of the specified group
# # # Use single quotes for the string and concatenate $GroupName
  
# # try {
# # # Log the start of the retrieval process
# # Write-EnhancedLog -Message "Attempting to retrieve members of the '$GroupName' group." -Level "INFO"

# # $groupPattern = [regex]::Escape($GroupName)
# # $admins = Get-WmiObject -Class Win32_GroupUser | Where-Object { $_.GroupComponent -match $groupPattern }
        
# # # Log the count of members found
# # $count = $admins.Count
# # Write-EnhancedLog -Message "Found $count members in the '$GroupName' group." -Level "INFO"
        
# # if ($count -gt 0) {
# # # Log details of each group member
# # Write-EnhancedLog -Message "Listing all members of the '$GroupName' group:" -Level "INFO"
# # foreach ($admin in $admins) {
# # # Extract the username from the PartComponent property
# # if ($admin.PartComponent -match 'Win32_UserAccount.Domain="[^"]+",Name="([^"]+)"') {
# # $accountName = $matches[1]
                        
# # # Log the extracted account name
# # Write-EnhancedLog -Message "Member: $accountName" -Level "INFO"
# # }
# # else {
# # # Log a message if the PartComponent does not match the expected pattern
# # Write-EnhancedLog -Message "Could not extract a valid account name from: $($admin.PartComponent)" -Level "WARNING"
# # }
# # }
                
# # }
# # else {
# # Write-EnhancedLog -Message "No members found in the '$GroupName' group." -Level "WARNING"
# # }
# # }
# # catch {
# # # Log the error if retrieval fails
# # Write-EnhancedLog -Message "Failed to retrieve members of the '$GroupName' group: $($_.Exception.Message)" -Level "ERROR"
# # Handle-Error -ErrorRecord $_
        
# # # Re-throw the exception to handle it further upstream if needed
# # throw
# # }

# # # Iterate over each group member
# # foreach ($admin in $admins) {
# # # Extract the account name from the PartComponent
# # if ($admin.PartComponent -match 'Win32_UserAccount.Domain="[^"]+",Name="([^"]+)"') {
# # $accountName = $matches[1]

# # # Try to resolve the account to see if it's a valid user or group
# # try {
# # $account = [ADSI]"WinNT://$($env:COMPUTERNAME)/$accountName"
# # Write-EnhancedLog -Message "Resolved account: $accountName" -Level "INFO"
# # }
# # catch {
# # # Add orphaned SIDs to the list
# # $orphanedSIDs.Add([pscustomobject]@{
# # AccountName = $accountName
# # SID = $_.PartComponent
# # })
# # Write-EnhancedLog -Message "Orphaned SID detected: $accountName" -Level "Warning"
# # }
# # }
# # }
# # }

# # End {
# # if ($orphanedSIDs.Count -eq 0) {
# # Write-EnhancedLog -Message "No orphaned SIDs found in the '$GroupName' group." -Level "INFO"
# # }
# # else {
# # Write-EnhancedLog -Message "Orphaned SIDs found: $($orphanedSIDs | Format-Table -AutoSize)" -Level "Warning"
# # }

# # Write-EnhancedLog -Message "Exiting Get-OrphanedSIDs function" -Level "Notice"
# # return $orphanedSIDs
# # }
# # }



# function Extract-AccountName {
# param (
# [string]$PartComponent
# )

# try {
# if ($PartComponent -match 'Win32_UserAccount.Domain="[^"]+",Name="([^"]+)"') {
# return $matches[1]
# }
# else {
# Write-EnhancedLog -Message "Could not extract a valid account name from: $PartComponent" -Level "WARNING"
# return $null
# }
# }
# catch {
# Write-EnhancedLog -Message "Failed to extract account name from PartComponent: $($_.Exception.Message)" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw
# }
# }






# function Resolve-Account {
# param (
# [string]$AccountName
# )

# try {
# $account = [ADSI]"WinNT://$($env:COMPUTERNAME)/$AccountName"
# Write-EnhancedLog -Message "Resolved account: $AccountName" -Level "INFO"
# return $true
# }
# catch {
# Write-EnhancedLog -Message "Orphaned SID detected: $AccountName" -Level "Warning"
# return $false
# }
# }
















# function Get-OrphanedSIDs {
# param (
# [string]$GroupName = "Administrators"
# )

# Begin {
# Write-EnhancedLog -Message "Starting Get-OrphanedSIDs function" -Level "Notice"
# $orphanedSIDs = [System.Collections.Generic.List[PSCustomObject]]::new()
# }

# Process {
# $admins = Get-GroupMembers -GroupName $GroupName

# if ($admins.Count -gt 0) {
# foreach ($admin in $admins) {
# $accountName = Extract-AccountName -PartComponent $admin.PartComponent

# if ($accountName) {
# $resolved = Resolve-Account -AccountName $accountName
# if (-not $resolved) {
# $orphanedSIDs.Add([pscustomobject]@{
# AccountName = $accountName
# SID = $admin.PartComponent
# })
# }
# }
# }
# }
# }

# End {
# if ($orphanedSIDs.Count -eq 0) {
# Write-EnhancedLog -Message "No orphaned SIDs found in the '$GroupName' group." -Level "INFO"
# }
# else {
# Write-EnhancedLog -Message "Orphaned SIDs found: $($orphanedSIDs | Format-Table -AutoSize)" -Level "Warning"
# }

# Write-EnhancedLog -Message "Exiting Get-OrphanedSIDs function" -Level "Notice"
# return $orphanedSIDs
# }
# }
#EndRegion '.\Public\Get-OrphanedSIDs-Archive.ps1' 211
#Region '.\Public\Install-ADKFromMSI.ps1' -1

function Install-ADKFromMSI {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$OfflinePath,

        [Parameter(Mandatory = $true)]
        [string]$ICDPath = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Imaging and Configuration Designer\x86\ICD.exe"
    )

    Begin {
        Write-EnhancedLog -Message "Starting Install-ADKFromMSI function" -Level "INFO"
        Log-Params -Params @{
            OfflinePath = $OfflinePath
            ICDPath = $ICDPath
        }

        # Ensure offline path exists
        if (-not (Test-Path -Path $OfflinePath)) {
            throw "Offline path not found: $OfflinePath"
        }
    }

    Process {
        try {
            # Get all MSI files in the offline path
            $MSIFiles = Get-ChildItem -Path $OfflinePath -Filter *.msi

            if (-not $MSIFiles) {
                throw "No MSI files found in: $OfflinePath"
            }

            # Install each MSI file
            foreach ($MSI in $MSIFiles) {
                Write-EnhancedLog -Message "Installing MSI: $($MSI.FullName)" -Level "INFO"
                Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$($MSI.FullName)`" /quiet /norestart" -Wait -NoNewWindow
            }

            # Check if ICD.exe exists
            if (Test-Path -Path $ICDPath) {
                Write-EnhancedLog -Message "ICD.exe found at: $ICDPath" -Level "INFO"
            } else {
                throw "ICD.exe not found at: $ICDPath"
            }

        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Install-ADKFromMSI function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Install-ADKFromMSI function" -Level "INFO"
    }
}

# # Example usage
# $installParams = @{
# OfflinePath = "$env:TEMP\ADKOffline\Installers"
# ICDPath = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Imaging and Configuration Designer\x86\ICD.exe"
# }

# Install-ADKFromMSI @installParams
#EndRegion '.\Public\Install-ADKFromMSI.ps1' 64
#Region '.\Public\Install-OneDriveSetup.ps1' -1

function Install-OneDriveSetup {
    param (
        [string]$ODSetupPath,
        [string]$SetupArgumentList
    )
    
    Write-Log "Installing OneDrive setup from $ODSetupPath..." -Level "INFO"
    $startProcessParams = @{
        FilePath     = $ODSetupPath
        ArgumentList = $SetupArgumentList
        Wait         = $true
        NoNewWindow  = $true
    }
    
    try {
        Start-Process @startProcessParams
        Write-Log "OneDrive installation completed." -Level "INFO"
    }
    catch {
        Write-Log "An error occurred during OneDrive installation: $($_.Exception.Message)" -Level "ERROR"
        throw $_
    }
}
#EndRegion '.\Public\Install-OneDriveSetup.ps1' 24
#Region '.\Public\Install-PPKG.ps1' -1

function Install-PPKG {
    <#
    .SYNOPSIS
    Installs a provisioning package (PPKG) with validation and logging.
 
    .DESCRIPTION
    The Install-PPKG function installs a provisioning package (PPKG) from a specified path. If the provisioning package is already installed, it will force remove it before reinstalling. It logs the installation process, validates the installation, and handles errors gracefully.
 
    .PARAMETER PPKGPath
    The full path to the provisioning package (PPKG) to be installed.
 
    .EXAMPLE
    $params = @{
        PPKGPath = "C:\ProgramData\AADMigration\MyProvisioningPackage.ppkg"
    }
    Install-PPKG @params
    Installs the specified provisioning package and logs the installation.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the full path to the PPKG file.")]
        [ValidateNotNullOrEmpty()]
        [string]$PPKGPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Install-PPKG function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Validate PPKG file exists
        if (-not (Test-Path -Path $PPKGPath)) {
            throw "Provisioning package file not found: $PPKGPath"
        }


        # Extract just the file name from the PPKG path (without extension)
        $ppkgFileName = [System.IO.Path]::GetFileNameWithoutExtension($PPKGPath)

        # Check if the PPKG is already installed
        Write-EnhancedLog -Message "Validating if provisioning package is already installed." -Level "INFO"
        $isInstalled = Validate-PPKGInstallation -PPKGName $ppkgFileName
  

        if ($isInstalled) {
            Remove-InstalledPPKG -PackageName $ppkgFileName
        }
        


        # Wait-Debugger
    }

    Process {
        try {
            Write-EnhancedLog -Message "Installing provisioning package: $PPKGPath" -Level "INFO"

            # Get current timestamp
            $timestamp = (Get-Date).ToString("yyyy-MM-dd_HH-mm-ss")

            # Create the dynamic log directory path based on PPKGPath and timestamp
            $logDir = "C:\logs\PPKG_EJ_Bulk_Enrollment\$ppkgFileName"
            $logFile = "$logDir\InstallLog_$timestamp.etl"

            # Check if the log directory exists, and create it if not
            if (-not (Test-Path $logDir)) {
                Write-EnhancedLog -Message "Log directory does not exist. Creating directory: $logDir" -Level "INFO"
                New-Item -Path $logDir -ItemType Directory -Force
            }

            # Set the Install-ProvisioningPackage parameters with the dynamic log file path
            $InstallProvisioningPackageParams = @{
                PackagePath   = $PPKGPath
                ForceInstall  = $true
                QuietInstall  = $true
                LogsDirectory = $logFile
            }

            # Log the parameters being passed to Install-ProvisioningPackage
            Write-EnhancedLog -Message "Starting installation of provisioning package with the following parameters:" -Level "INFO"
            Log-Params -Params $InstallProvisioningPackageParams

            # Install the provisioning package
            Install-ProvisioningPackage @InstallProvisioningPackageParams
            Write-EnhancedLog -Message "Successfully installed provisioning package: $PPKGPath" -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "Error installing provisioning package: $($_.Exception.Message)" -Level "ERROR"
            Write-EnhancedLog -Message "Did you check that the Package_GUID Account in Entra is excluded from All Conditional Access Policies" -Level "INFO"
            Handle-Error -ErrorRecord $_
            throw
        }
        Finally {
            # Always log exit and cleanup actions
            Write-EnhancedLog -Message "Exiting Install-PPKG function" -Level "Notice"
        }
    }

    End {
        Write-EnhancedLog -Message "Validating provisioning package installation" -Level "INFO"
        $isInstalled = Validate-PPKGInstallation -PPKGName $ppkgFileName
        if ($isInstalled) {
            Write-EnhancedLog -Message "Provisioning package $PPKGPath installed successfully" -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "Provisioning package $PPKGPath installation failed" -Level "ERROR"
            throw "Provisioning package $PPKGPath installation could not be validated"
        }
    }
}

# # Example usage
# $params = @{
# PPKGPath = "C:\ProgramData\AADMigration\Files\ICTC_Project_2_Aug_29_2024\ICTC_Project_2.ppkg"
# }
# Install-PPKG @params
#EndRegion '.\Public\Install-PPKG.ps1' 117
#Region '.\Public\Install-Software.ps1' -1

# Main function to manage the entire OneDrive installation and configuration
function Install-Software {
    <#
    .SYNOPSIS
        Installs a specified software and performs pre- and post-installation validation.
 
    .DESCRIPTION
        This function handles the installation of software by downloading the installer, validating the software before and after installation,
        and performing any necessary post-installation tasks such as syncing or configuring the software.
 
    .PARAMETER MigrationPath
        The base directory path where the setup file will be stored.
 
    .PARAMETER SoftwareName
        The name of the software to be installed, used for validation.
 
    .PARAMETER SetupUri
        The URL from which the setup executable will be downloaded.
 
    .PARAMETER SetupFile
        The name of the setup executable file.
 
    .PARAMETER RegKey
        The registry key path used for validating the installed version.
 
    .PARAMETER MinVersion
        The minimum required version of the software to validate the installation.
 
    .PARAMETER ExePath
        The path to the executable file used for file-based validation.
 
    .PARAMETER ScheduledTaskName
        The name of the scheduled task used for any post-installation tasks.
 
    .PARAMETER ScheduledTaskDescription
        A description for the scheduled task.
 
    .PARAMETER SetupArgumentList
        The arguments passed to the installer executable during installation.
 
    .PARAMETER KFM
        Specifies whether to perform a Known Folder Move (KFM) sync after installation. Default is $false.
 
    .PARAMETER TimestampPrefix
        A prefix used for naming the timestamped folder in the TEMP directory. Default is 'Setup_'.
 
    .EXAMPLE
        $installParams = @{
            MigrationPath = "C:\Migration"
            SoftwareName = "OneDrive"
            SetupUri = "https://go.microsoft.com/fwlink/?linkid=844652"
            SetupFile = "OneDriveSetup.exe"
            RegKey = "HKLM:\SOFTWARE\Microsoft\OneDrive"
            MinVersion = [version]"23.143.0712.0001"
            ExePath = "C:\Program Files\Microsoft OneDrive\OneDrive.exe"
            ScheduledTaskName = "OneDriveRemediation"
            ScheduledTaskDescription= "Restart OneDrive to kick off KFM sync"
            SetupArgumentList = "/allusers"
            KFM = $true
            TimestampPrefix = "OneDriveSetup_"
        }
        Install-Software @installParams
 
    .NOTES
        Author: Abdullah Ollivierre
        Date: 2024-08-15
    #>


    [CmdletBinding()]
    param (
        [string]$MigrationPath,
        [string]$SoftwareName,
        [string]$SetupUri,
        [string]$SetupFile,
        [string]$RegKey,
        [version]$MinVersion,
        [string]$ExePath,
        [string]$ScheduledTaskName,
        [string]$ScheduledTaskDescription,
        [string]$SetupArgumentList,
        [bool]$KFM = $false,
        [string]$TimestampPrefix # Default prefix for the timestamped folder
    )

    Begin {
        Write-EnhancedLog -Message "Starting Install-Software function for $SoftwareName" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Ensure the script is running with elevated privileges
        CheckAndElevate

        # Generate a timestamped folder within the TEMP directory
        $timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss")
        $destinationFolder = [System.IO.Path]::Combine($env:TEMP, "$TimestampPrefix$timestamp")
        $SetupPath = [System.IO.Path]::Combine($destinationFolder, $SetupFile)
    }

    Process {
        # Step 1: Pre-installation validation
        Write-EnhancedLog -Message "Step 1: Performing pre-installation validation for $SoftwareName..." -Level "INFO"
        $preInstallParams = @{
            SoftwareName        = $SoftwareName
            MinVersion          = $MinVersion
            RegistryPath        = $RegKey
            ExePath             = $ExePath
            MaxRetries          = 3
            DelayBetweenRetries = 5
        }
        $preInstallCheck = Validate-SoftwareInstallation @preInstallParams
        if ($preInstallCheck.IsInstalled) {
            Write-EnhancedLog -Message "$SoftwareName version $($preInstallCheck.Version) is already installed. Skipping installation." -Level "INFO"
            return
        }
        Write-EnhancedLog -Message "$SoftwareName is not currently installed or needs an update." -Level "INFO"

        # Step 2: Download the setup file if not already present
        Write-EnhancedLog -Message "Step 2: Downloading $SoftwareName setup..." -Level "INFO"
        if (-not (Test-Path -Path $SetupPath)) {
            Download-OneDriveSetup -ODSetupUri $SetupUri -ODSetupPath $SetupPath
        } else {
            Write-EnhancedLog -Message "$SoftwareName setup already downloaded at $SetupPath" -Level "INFO"
        }

        # Step 3: Install the software
        Write-EnhancedLog -Message "Step 3: Installing $SoftwareName..." -Level "INFO"
        Install-OneDriveSetup -ODSetupPath $SetupPath -SetupArgumentList $SetupArgumentList

        # Step 4: Post-installation validation
        Write-EnhancedLog -Message "Step 4: Performing post-installation validation for $SoftwareName..." -Level "INFO"
        $postInstallCheck = Validate-SoftwareInstallation @preInstallParams
        if ($postInstallCheck.IsInstalled) {
            Write-EnhancedLog -Message "$SoftwareName version $($postInstallCheck.Version) installed successfully." -Level "INFO"
        } else {
            Write-EnhancedLog -Message "$SoftwareName installation failed." -Level "ERROR"
            throw "$SoftwareName installation validation failed."
        }

        # Step 5: Perform KFM sync if enabled
        if ($KFM) {
            Write-EnhancedLog -Message "Step 5: Performing KFM sync for $SoftwareName..." -Level "INFO"
            Perform-KFMSync -OneDriveExePath $ExePath -ScheduledTaskName $ScheduledTaskName -ScheduledTaskDescription $ScheduledTaskDescription
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Install-Software function for $SoftwareName" -Level "NOTICE"
    }
}



# $installParams = @{
# MigrationPath = "C:\ProgramData\AADMigration"
# SoftwareName = "OneDrive"
# SetupUri = "https://go.microsoft.com/fwlink/?linkid=844652"
# SetupFile = "OneDriveSetup.exe"
# RegKey = "HKLM:\SOFTWARE\Microsoft\OneDrive"
# MinVersion = [version]"24.146.0721.0003"
# ExePath = "C:\Program Files\Microsoft OneDrive\OneDrive.exe"
# ScheduledTaskName = "OneDriveRemediation"
# ScheduledTaskDescription = "Restart OneDrive to kick off KFM sync"
# SetupArgumentList = "/allusers"
# KFM = $true
# TimestampPrefix = "OneDriveSetup_"
# }

# Install-Software @installParams
#EndRegion '.\Public\Install-Software.ps1' 168
#Region '.\Public\Invoke-BitlockerEscrow.ps1' -1


function Invoke-BitlockerEscrow {
    <#
    .SYNOPSIS
    Escrows the BitLocker recovery key to Azure AD.
 
    .DESCRIPTION
    The Invoke-BitlockerEscrow function escrows the BitLocker recovery key for the specified drive to Azure AD.
 
    .PARAMETER BitlockerDrive
    The drive letter of the BitLocker protected drive.
 
    .PARAMETER BitlockerKey
    The key protector ID to be escrowed.
 
    .EXAMPLE
    Invoke-BitlockerEscrow -BitlockerDrive "C:" -BitlockerKey "12345678-1234-1234-1234-123456789012"
    Escrows the BitLocker recovery key for drive C: to Azure AD.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$BitlockerDrive,

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

    Begin {
        Write-EnhancedLog -Message "Starting Invoke-BitlockerEscrow function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            Write-EnhancedLog -Message "Escrowing the BitLocker recovery key to Azure AD for drive: $BitlockerDrive" -Level "INFO"
            BackupToAAD-BitLockerKeyProtector -MountPoint $BitlockerDrive -KeyProtectorId $BitlockerKey -ErrorAction SilentlyContinue
            Write-EnhancedLog -Message "Attempted to escrow key in Azure AD - Please verify manually!" -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while escrowing the BitLocker key to Azure AD" -Level "ERROR"
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Invoke-BitlockerEscrow function" -Level "Notice"
    }
}
#EndRegion '.\Public\Invoke-BitlockerEscrow.ps1' 51
#Region '.\Public\Invoke-VaultDecryptionProcess.ps1' -1

function Invoke-VaultDecryptionProcess {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Enter your GitHub Personal Access Token (PAT) as a SecureString.")]
        [SecureString] $SecurePAT,
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the GitHub repository owner.")]
        [string] $RepoOwner = "aollivierre",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the GitHub repository name.")]
        [string] $RepoName = "Vault",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the release tag version.")]
        [string] $ReleaseTag = "0.1",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the file name to download from the release.")]
        [string] $FileName = "vault.GH.Asset.zip",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the destination path for the downloaded file.")]
        [string] $DestinationPath = "C:\temp\vault.GH.Asset.zip",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the path to the ZIP file to be decrypted.")]
        [string] $ZipFilePath = "C:\temp\vault.zip",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the path to the Base64-encoded certificate file.")]
        [string] $CertBase64Path = "C:\temp\vault\certs\cert.pfx.base64",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the path to the certificate password text file.")]
        [string] $CertPasswordPath = "C:\temp\vault\certs\certpassword.txt",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the path to the encrypted AES key in Base64 format.")]
        [string] $KeyBase64Path = "C:\temp\vault\certs\secret.key.encrypted.base64",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the path to the encrypted file that needs to be decrypted.")]
        [string] $EncryptedFilePath = "C:\temp\vault\vault.zip.encrypted",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the directory for storing certificate and key-related temporary files.")]
        [string] $CertsDir = "C:\temp\vault\certs",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the path to store the decrypted file.")]
        [string] $DecryptedFilePath = "C:\temp\vault.zip",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the KeePass database file path.")]
        [string] $KeePassDatabasePath = "C:\temp\vault-decrypted\myDatabase.kdbx",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the KeePass key file path.")]
        [string] $KeyFilePath = "C:\temp\vault-decrypted\myKeyFile.keyx",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the KeePass entry name.")]
        [string] $EntryName = "ICTC-EJ-PPKG",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the attachment name in KeePass to be exported.")]
        [string] $AttachmentName = "ICTC_Project_2_Aug_29_2024.zip",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the export path for the KeePass attachment.")]
        [string] $ExportPath = "C:\temp\vault-decrypted\ICTC_Project_2_Aug_29_2024-fromdb.zip",
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the destination directory where the final decrypted and exported files will be placed.")]
        [string] $FinalDestinationDirectory = "C:\temp\vault-decrypted"
    )

    Begin {
        # Log the parameters
        Write-EnhancedLog -Message "Starting Invoke-VaultDecryptionProcess" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Convert SecureString to plain text
        try {
            $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePAT)
            $pat = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr)
        }
        catch {
            Handle-Error -Message "Failed to convert SecureString to plain text." -ErrorRecord $_
        }
    }

    Process {
        try {
            # Step 1: Download the GitHub Release Asset
            $downloadParams = @{
                Token           = $pat
                RepoOwner       = $RepoOwner
                RepoName        = $RepoName
                ReleaseTag      = $ReleaseTag
                FileName        = $FileName
                DestinationPath = $DestinationPath
            }
            Write-EnhancedLog -Message "Downloading GitHub Release Asset..." -Level 'INFO'
            Download-GitHubReleaseAsset @downloadParams

            # Step 2: Unzip the downloaded file
            $unzipParams = @{
                ZipFilePath          = $DestinationPath
                DestinationDirectory = "C:\temp\vault"
            }
            Write-EnhancedLog -Message "Unzipping the downloaded asset..." -Level 'INFO'
            Unzip-Directory @unzipParams

            # Step 3: Decrypt the file using the certificate and AES
            $decryptParams = @{
                CertBase64Path    = $CertBase64Path
                CertPasswordPath  = $CertPasswordPath
                KeyBase64Path     = $KeyBase64Path
                EncryptedFilePath = $EncryptedFilePath
                DecryptedFilePath = $DecryptedFilePath
                CertsDir          = $CertsDir
            }
            Write-EnhancedLog -Message "Decrypting the file using AES + RSA (Cert)..." -Level 'INFO'
            Decrypt-FileWithCert @decryptParams

            # Step 4: Unzip the decrypted file
            $unzipDecryptedParams = @{
                ZipFilePath          = $ZipFilePath
                DestinationDirectory = $FinalDestinationDirectory
            }
            Write-EnhancedLog -Message "Unzipping the decrypted file..." -Level 'INFO'
            Unzip-Directory @unzipDecryptedParams

            # Step 5: Export the attachment from KeePass
            $exportAttachmentParams = @{
                DatabasePath   = $KeePassDatabasePath
                KeyFilePath    = $KeyFilePath
                EntryName      = $EntryName
                AttachmentName = $AttachmentName
                ExportPath     = $ExportPath
            }
            Write-EnhancedLog -Message "Exporting attachment from KeePass database..." -Level 'INFO'
            Export-KeePassAttachment @exportAttachmentParams

            # Step 6: Unzip the final exported attachment
            $unzipFinalParams = @{
                ZipFilePath          = $ExportPath
                DestinationDirectory = $FinalDestinationDirectory
            }
            Write-EnhancedLog -Message "Unzipping the final exported attachment..." -Level 'INFO'
            Unzip-Directory @unzipFinalParams

            Write-EnhancedLog -Message "Process completed successfully!" -Level 'INFO'
        }
        catch {
            Handle-Error -Message "An error occurred during the process." -ErrorRecord $_
        }
    }

    End {
        # Clean up secure data
        try {
            $pat = $null
            [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr)
        }
        catch {
            Handle-Error -Message "Failed to clean up secure data." -ErrorRecord $_
        }

        Write-EnhancedLog -Message "Exiting Invoke-VaultDecryptionProcess" -Level "Notice"
    }
}
#EndRegion '.\Public\Invoke-VaultDecryptionProcess.ps1' 158
#Region '.\Public\Leave-Domain.ps1' -1


function Leave-Domain {
    <#
        .SYNOPSIS
            Removes the computer from the domain.
         
        .DESCRIPTION
            This function attempts to remove the computer from the domain using provided credentials. If domain credentials fail, it falls back to using local credentials.
         
        .PARAMETER DomainLeaveUser
            The domain user account to use for leaving the domain.
         
        .PARAMETER DomainLeavePassword
            The password for the domain user account.
         
        .PARAMETER ComputerName
            The name of the computer to remove from the domain.
         
        .PARAMETER TempUser
            The temporary local user to use if domain credentials fail.
         
        .PARAMETER TempUserPassword
            The password for the temporary local user.
         
        .EXAMPLE
            Leave-Domain -DomainLeaveUser "AdminUser" -DomainLeavePassword "P@ssw0rd" -ComputerName "localhost" -TempUser "LocalAdmin" -TempUserPassword "P@ssw0rd"
         
        .NOTES
            This function removes the computer from the domain, using domain credentials if possible.
        #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$DomainLeaveUser,
        
        [Parameter(Mandatory = $false)]
        [string]$DomainLeavePassword,
        
        [Parameter(Mandatory = $true)]
        [string]$ComputerName,
        
        [Parameter(Mandatory = $true)]
        [string]$TempUser,
        
        [Parameter(Mandatory = $true)]
        [string]$TempUserPassword
    )
        
    Begin {
        Write-EnhancedLog -Message "Starting Leave-Domain function" -Level "NOTICE"
    }
        
    Process {
        if ($DomainLeaveUser) {
            $SecurePassword = ConvertTo-SecureString -String $DomainLeavePassword -AsPlainText -Force
            $Credentials = New-Object System.Management.Automation.PSCredential($DomainLeaveUser, $SecurePassword)
        
            try {
                Remove-Computer -ComputerName $ComputerName -Credential $Credentials -Verbose -Force -ErrorAction Stop
            }
            catch {
                Write-EnhancedLog -Message "Leaving domain with domain credentials failed. Will leave domain with local account." -Level "ERROR"
                # Fallback to local user
                $SecurePassword = ConvertTo-SecureString -String $TempUserPassword -AsPlainText -Force
                $Credentials = New-Object System.Management.Automation.PSCredential($TempUser, $SecurePassword)
                Remove-Computer -ComputerName $ComputerName -Credential $Credentials -Verbose -Force
            }
        }
    }
        
    End {
        Write-EnhancedLog -Message "Exiting Leave-Domain function" -Level "NOTICE"
    }
}
#EndRegion '.\Public\Leave-Domain.ps1' 75
#Region '.\Public\Main-MigrateToAADJOnly.ps1' -1

function Main-MigrateToAADJOnly {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$PPKGPath,
        
        [Parameter(Mandatory = $false)]
        [string]$DomainLeaveUser,
        
        [Parameter(Mandatory = $false)]
        [string]$DomainLeavePassword,
        
        [Parameter(Mandatory = $true)]
        [string]$TempUser,
        
        [Parameter(Mandatory = $true)]
        [string]$TempUserPassword,
        
        [Parameter(Mandatory = $true)]
        [string]$ScriptPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Main-MigrateToAADJOnly function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Test provisioning package
            # $TestProvisioningPackParams = @{
            # PPKGName = $PPKGName
            # }
            # Test-ProvisioningPack @TestProvisioningPackParams

            # Validate PPKG file exists
            if (-not (Test-Path -Path $PPKGPath)) {
                throw "Provisioning package file not found: $PPKGPath"
            }


            # Extract just the file name from the PPKG path (without extension)
            $ppkgFileName = [System.IO.Path]::GetFileNameWithoutExtension($PPKGPath)

            # Check if the PPKG is already installed
            Write-EnhancedLog -Message "Validating if provisioning package is already installed." -Level "INFO"
            $isInstalled = Validate-PPKGInstallation -PPKGName $ppkgFileName
  

            if ($isInstalled) {
                Remove-InstalledPPKG -PackageName $ppkgFileName
            }

            # Add local user
            # $AddLocalUserParams = @{
            # TempUser = $TempUser
            # TempUserPassword = $TempUserPassword
            # Description = "account for autologin"
            # Group = "Administrators"
            # }
            # Add-LocalUser @AddLocalUserParams


            $EnsureUserInLocalAdminGroupParams = @{
                Username    = $TempUser
                Password    = $TempUserPassword
                Description = "account for autologin"
            }

            Ensure-UserInLocalAdminGroup @EnsureUserInLocalAdminGroupParams

            # Wait-Debugger

            # Set autologin
            $SetAutologinParams = @{
                TempUser            = $TempUser
                TempUserPassword    = $TempUserPassword
                RegPath             = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
                AutoAdminLogonName  = 'AutoAdminLogon'
                AutoAdminLogonValue = '1'
                DefaultUsernameName = 'DefaultUsername'
                DefaultPasswordName = 'DefaultPassword'
            }
            Set-Autologin @SetAutologinParams

            # Disable OOBE privacy
            $DisableOOBEPrivacyParams = @{
                OOBERegistryPath      = 'HKLM:\Software\Policies\Microsoft\Windows\OOBE'
                OOBEName              = 'DisablePrivacyExperience'
                OOBEValue             = '1'
                AnimationRegistryPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
                AnimationName         = 'EnableFirstLogonAnimation'
                AnimationValue        = '0'
                LockRegistryPath      = 'HKLM:\Software\Policies\Microsoft\Windows\Personalization'
                LockName              = 'NoLockScreen'
                LockValue             = '1'
            }
            Disable-OOBEPrivacy @DisableOOBEPrivacyParams

            # Set RunOnce script
            $SetRunOnceParams = @{
                ScriptPath      = $ScriptPath
                RunOnceKey      = "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
                PowershellPath  = "C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe"
                ExecutionPolicy = "Unrestricted"
                RunOnceName     = "NextRun"
            }
            Set-RunOnce @SetRunOnceParams

            # Suspend BitLocker with reboot count
            $SuspendBitLockerWithRebootParams = @{
                MountPoint  = "C:"
                RebootCount = 3
            }
            Suspend-BitLockerWithReboot @SuspendBitLockerWithRebootParams

            # Remove Intune management
            $RemoveIntuneMgmtParams = @{
                OMADMPath             = "HKLM:\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts\*"
                EnrollmentBasePath    = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments"
                TrackedBasePath       = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EnterpriseResourceManager\Tracked"
                PolicyManagerBasePath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager"
                ProvisioningBasePath  = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning"
                CertCurrentUserPath   = "cert:\CurrentUser"
                CertLocalMachinePath  = "cert:\LocalMachine"
                TaskPathBase          = "\Microsoft\Windows\EnterpriseMgmt"
                MSDMProviderID        = "MS DM Server"
                RegistryPathsToRemove = @(
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments\Status",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EnterpriseResourceManager\Tracked",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\AdmxInstalled",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\Providers",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\OMADM\Logger",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\OMADM\Sessions"
                )
                UserCertIssuer        = "CN=SC_Online_Issuing"
                DeviceCertIssuers     = @("CN=Microsoft Intune Root Certification Authority", "CN=Microsoft Intune MDM Device CA")
            }
            Remove-IntuneMgmt @RemoveIntuneMgmtParams



            Perform-IntuneCleanup


            # Remove hybrid join
            Remove-Hybrid

            # Remove AD join
            $RemoveADJoinParams = @{
                TempUser         = $TempUser
                TempUserPassword = $TempUserPassword
                ComputerName     = "localhost"
            }
            Remove-ADJoin @RemoveADJoinParams

            # Restart-ComputerIfNeeded

        }
        catch {
            Write-EnhancedLog -Message "An error occurred: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Main-MigrateToAADJOnly function" -Level "Notice"
    }
}


# $MainMigrateParams = @{
# PPKGName = "YourProvisioningPackName"
# DomainLeaveUser = "YourDomainUser"
# DomainLeavePassword = "YourDomainPassword"
# TempUser = "YourTempUser"
# TempUserPassword = "YourTempUserPassword"
# ScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce.ps1"
# }

# Main-MigrateToAADJOnly @MainMigrateParams
#EndRegion '.\Public\Main-MigrateToAADJOnly.ps1' 184
#Region '.\Public\Manage-LocalUserAccounts.ps1' -1

function Manage-LocalUserAccounts {
    <#
    .SYNOPSIS
    Manages local user accounts by enabling them in Dev mode or disabling them in Prod mode.
   
    .DESCRIPTION
    The Manage-LocalUserAccounts function enables all local user accounts in Dev mode and disables them in Prod mode.
   
    .PARAMETER Mode
    Specifies the mode in which the script should run. Options are "Dev" for development mode or "Prod" for production mode.
   
    .EXAMPLE
    Manage-LocalUserAccounts -Mode Dev
    Enables all local user accounts in Dev mode.
   
    .EXAMPLE
    Manage-LocalUserAccounts -Mode Prod
    Disables all local user accounts in Prod mode.
    #>

  
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet("Dev", "Prod")]
        [string]$Mode
    )
  
    Begin {
        Write-EnhancedLog -Message "Starting Manage-LocalUserAccounts function in $Mode mode" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
  
    Process {
        try {
            if ($Mode -eq "Dev") {
                # Enable local user accounts in Dev mode
                Write-EnhancedLog -Message "Enabling local user accounts in $Mode mode" -Level "INFO"
                Enable-LocalUserAccounts
                Write-EnhancedLog -Message "Local user accounts enabled in $Mode mode" -Level "INFO"
            }
            elseif ($Mode -eq "Prod") {
                # Disable local user accounts in Prod mode
                Write-EnhancedLog -Message "Disabling local user accounts in $Mode mode" -Level "INFO"
                Disable-LocalUserAccounts
                Write-EnhancedLog -Message "Local user accounts disabled in $Mode mode" -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Manage-LocalUserAccounts function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
  
    End {
        Write-EnhancedLog -Message "Exiting Manage-LocalUserAccounts function" -Level "Notice"
    }
}

# Example usage
# Manage-LocalUserAccounts -Mode Dev
# Manage-LocalUserAccounts -Mode Prod
#EndRegion '.\Public\Manage-LocalUserAccounts.ps1' 63
#Region '.\Public\Manage-NetworkAdapters.ps1' -1

function Manage-NetworkAdapters {
    <#
                .SYNOPSIS
                    Manages the state of network adapters.
                 
                .DESCRIPTION
                    This function disables or enables all connected network adapters based on the provided parameter.
                 
                .PARAMETER Disable
                    Switch parameter to disable network adapters. If not provided, the function will enable the adapters.
                 
                .EXAMPLE
                    Manage-NetworkAdapters -Disable
                 
                .EXAMPLE
                    Manage-NetworkAdapters
                 
                .NOTES
                    This function is useful for temporarily disabling network connectivity during specific operations.
                #>

    [CmdletBinding()]
    param (
        [switch]$Disable
    )
                
    Begin {
        Write-EnhancedLog -Message "Starting Manage-NetworkAdapters function" -Level "NOTICE"
    }
                
    Process {
        $ConnectedAdapters = Get-NetAdapter | Where-Object { $_.MediaConnectionState -eq "Connected" }
                
        foreach ($Adapter in $ConnectedAdapters) {
            if ($Disable) {
                Write-EnhancedLog -Message "Disabling network adapter $($Adapter.Name)" -Level "INFO"
                Disable-NetAdapter -Name $Adapter.Name -Confirm:$false
            }
            else {
                Write-EnhancedLog -Message "Enabling network adapter $($Adapter.Name)" -Level "INFO"
                Enable-NetAdapter -Name $Adapter.Name -Confirm:$false
            }
        }
    }
                
    End {
        Write-EnhancedLog -Message "Exiting Manage-NetworkAdapters function" -Level "NOTICE"
    }
}
#EndRegion '.\Public\Manage-NetworkAdapters.ps1' 49
#Region '.\Public\Manage-UserSessions.ps1' -1

# Function to filter and log interactive user sessions
function Get-InteractiveUsers {
    try {
        # Log the start of the function
        Write-EnhancedLog -Message "Starting Get-InteractiveUsers function" -Level "NOTICE"

        # Run the 'query user' command to get logged-in user sessions
        $sessions = query user | Select-Object -Skip 1 | ForEach-Object {
            # Parse each line to extract session information, skip invalid lines
            # Split on 2 or more spaces to handle spacing variations
            $fields = $_ -split '\s{2,}'

            # Some lines may not have all fields, skip them
            if ($fields.Count -ge 4) {
                [PSCustomObject]@{
                    UserName    = $fields[0]
                    SessionName = $fields[1]
                    ID          = $fields[2]
                    State       = $fields[3]
                }
            }
        }

        # Filter out system accounts and background service accounts
        # Wrap in @() to ensure it's always treated as an array
        $interactiveUsers = @($sessions | Where-Object {
                $_.UserName -notmatch "^(DWM-|UMFD-|SYSTEM|LOCAL SERVICE|NETWORK SERVICE)"
            })

        # Log the users found
        Write-EnhancedLog -Message "Found $($interactiveUsers.Count) interactive users" -Level "INFO"

        return $interactiveUsers

    }
    catch {
        Handle-Error -ErrorRecord $_
    }
    finally {
        Write-EnhancedLog -Message "Exiting Get-InteractiveUsers function" -Level "NOTICE"
    }
}


# Function to handle the interactive user session logic
# function Manage-UserSessions {
# try {
# Write-EnhancedLog -Message "Starting Manage-UserSessions function" -Level "NOTICE"
        
# # Ensure $interactiveUsers is always treated as an array
# $interactiveUsers = @(Get-InteractiveUsers)

# if ($interactiveUsers.Count -gt 1) {
# # Log multiple users and throw an error
# Write-EnhancedLog -Message "Multiple interactive users logged in" -Level "WARNING"
# $interactiveUsers | ForEach-Object {
# Write-EnhancedLog -Message "User: $($_.UserName)" -Level "WARNING"
# }
# throw "Error: More than one interactive user is logged in. Please log off all other user sessions except the currently logged-in one."
# }
# elseif ($interactiveUsers.Count -eq 1) {
# # Log the single user and success message
# Write-EnhancedLog -Message "Interactive user logged in: $($interactiveUsers[0].UserName)" -Level "INFO"
# Write-EnhancedLog -Message "Success: Only one interactive user is logged in." -Level "NOTICE"
# }
# else {
# # Log no users
# Write-EnhancedLog -Message "No interactive users are currently logged in." -Level "ERROR"
# }

# } catch {
# Handle-Error -ErrorRecord $_
# } finally {
# Write-EnhancedLog -Message "Exiting Manage-UserSessions function" -Level "NOTICE"
# }
# }


function Show-UserSessionWarningForm {
    param (
        [array]$Users
    )
    

    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing

    $form = New-Object system.windows.forms.Form
    $form.Text = "Multiple Interactive Sessions Detected"
    $form.Size = New-Object System.Drawing.Size(400, 300)
    $form.StartPosition = "CenterScreen"
    
    # Create a label for warning
    $label = New-Object system.windows.forms.Label
    $label.Text = "Multiple interactive sessions are logged in. Please log off all other sessions."
    $label.Size = New-Object System.Drawing.Size(360, 40)
    $label.Location = New-Object System.Drawing.Point(20, 20)
    $form.Controls.Add($label)

    # Create a listbox to show interactive users
    $listBox = New-Object system.windows.forms.ListBox
    $listBox.Size = New-Object System.Drawing.Size(350, 100)
    $listBox.Location = New-Object System.Drawing.Point(20, 70)
    $Users | ForEach-Object { $listBox.Items.Add($_.UserName) }
    $form.Controls.Add($listBox)

    # Create OK and Cancel buttons
    $okButton = New-Object system.windows.forms.Button
    $okButton.Text = "OK"
    $okButton.Location = New-Object System.Drawing.Point(220, 200)
    $okButton.Add_Click({
            $form.DialogResult = [System.Windows.Forms.DialogResult]::OK
            $form.Close()
        })
    $form.Controls.Add($okButton)

    $cancelButton = New-Object system.windows.forms.Button
    $cancelButton.Text = "Cancel"
    $cancelButton.Location = New-Object System.Drawing.Point(120, 200)
    $cancelButton.Add_Click({
            $form.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
            $form.Close()
        })
    $form.Controls.Add($cancelButton)

    # Show the form
    $form.Topmost = $true
    return $form.ShowDialog()
}


function Manage-UserSessions {
    [CmdletBinding()]
    param ()

  

    # Display the form to warn the user about multiple sessions
  

    try {
        Write-EnhancedLog -Message "Starting Manage-UserSessions function" -Level "NOTICE"

        # Loop until only one interactive user is logged in
        do {
            # Retrieve all interactive users
            $interactiveUsers = @(Get-InteractiveUsers)
            
            if ($interactiveUsers.Count -gt 1) {
                # Log multiple users and show warning
                Write-EnhancedLog -Message "Multiple interactive users logged in" -Level "WARNING"
                $interactiveUsers | ForEach-Object {
                    Write-EnhancedLog -Message "User: $($_.UserName)" -Level "WARNING"
                }

                # Show the Windows Form with user warning
                $result = Show-UserSessionWarningForm -Users $interactiveUsers

                # If user cancels, stop the script
                if ($result -eq [System.Windows.Forms.DialogResult]::Cancel) {
                    Write-EnhancedLog -Message "User canceled the session management." -Level "ERROR"
                    throw "User canceled the session management."
                }

            }
            elseif ($interactiveUsers.Count -eq 1) {
                # Log success when only one user is logged in
                Write-EnhancedLog -Message "Interactive user logged in: $($interactiveUsers[0].UserName)" -Level "INFO"
                Write-EnhancedLog -Message "Success: Only one interactive user is logged in." -Level "NOTICE"
                break
            }
            else {
                Write-EnhancedLog -Message "No interactive users are currently logged in." -Level "ERROR"
                throw "No interactive users are currently logged in."
            }

            # Sleep before the next check
            Start-Sleep -Seconds 5

        } until ($interactiveUsers.Count -eq 1)

    }
    catch {
        Handle-Error -ErrorRecord $_
    }
    finally {
        Write-EnhancedLog -Message "Exiting Manage-UserSessions function" -Level "NOTICE"
    }
}



# # Main execution
# try {
# Write-EnhancedLog -Message "Script execution started" -Level "NOTICE"
# Manage-UserSessions
# } catch {
# Handle-Error -ErrorRecord $_
# } finally {
# Write-EnhancedLog -Message "Script execution finished" -Level "NOTICE"
# }
#EndRegion '.\Public\Manage-UserSessions.ps1' 202
#Region '.\Public\Perform-KFMSync.ps1' -1

function Perform-KFMSync {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$OneDriveExePath,

        [Parameter(Mandatory = $true)]
        [string]$ScheduledTaskName,

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

    Begin {
        Write-EnhancedLog -Message "Starting Perform-KFMSync function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            Write-EnhancedLog -Message "Performing KFM sync" -Level "INFO"
            $ODProcess = Get-Process -Name OneDrive -ErrorAction SilentlyContinue

            if ($ODProcess) {
                $ODProcess | Stop-Process -Confirm:$false -Force
                Start-Sleep -Seconds 5

                Unregister-ScheduledTaskWithLogging -TaskName $ScheduledTaskName

                $CreateOneDriveRemediationTaskParams = @{
                    OneDriveExePath           = $OneDriveExePath
                    ScheduledTaskName         = $ScheduledTaskName
                    ScheduledTaskDescription  = $ScheduledTaskDescription
                }
                
                Create-OneDriveRemediationTask @CreateOneDriveRemediationTaskParams
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while performing KFM sync: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Perform-KFMSync function" -Level "Notice"
    }
}
#EndRegion '.\Public\Perform-KFMSync.ps1' 50
#Region '.\Public\PostRunOnce-Phase1EntraJoin.ps1' -1

function PostRunOnce-Phase1EntraJoin {
    <#
    .SYNOPSIS
    Starts the migration process by configuring settings, blocking user input, displaying a progress form, installing a provisioning package, and optionally restarting the computer.
 
    .DESCRIPTION
    The PostRunOnce-Phase1EntraJoin function configures migration settings, blocks user input, displays a migration progress form, sets a RunOnce script for post-reboot tasks, installs a provisioning package, and then optionally restarts the computer. The function includes validation of the provisioning package installation using the Get-ProvisioningPackage cmdlet.
 
    .PARAMETER MigrationConfigPath
    The path to the migration configuration file.
 
    .PARAMETER ImagePath
    The path to the image file to be displayed on the migration progress form.
 
    .PARAMETER RunOnceScriptPath
    The path to the PowerShell script to be executed on the next system startup.
 
    .PARAMETER RunOnceKey
    The registry key path for the RunOnce entry.
 
    .PARAMETER PowershellPath
    The path to the PowerShell executable.
 
    .PARAMETER ExecutionPolicy
    The execution policy for running the PowerShell script.
 
    .PARAMETER RunOnceName
    The name of the RunOnce entry.
 
    .PARAMETER RebootAfterInstallation
    A switch parameter that controls whether the computer should be restarted after the provisioning package installation. If not specified, the computer will be restarted by default.
 
    .PARAMETER Mode
    Specifies the mode in which the script should run. Options are "Dev" for development mode or "Prod" for production mode. In Dev mode, certain features are skipped, while in Prod mode, all features are executed.
 
    .EXAMPLE
    $params = @{
        MigrationConfigPath = "C:\ProgramData\AADMigration\scripts\MigrationConfig.psd1"
        ImagePath = "C:\ProgramData\AADMigration\Files\MigrationInProgress.bmp"
        RunOnceScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce2.ps1"
        RunOnceKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
        PowershellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe"
        ExecutionPolicy = "Unrestricted"
        RunOnceName = "NextRun"
        Mode = "Dev"
    }
    PostRunOnce-Phase1EntraJoin @params
    Runs the migration process in Dev mode, skipping user input blocking and reboots.
 
    .EXAMPLE
    $params = @{
        MigrationConfigPath = "C:\ProgramData\AADMigration\scripts\MigrationConfig.psd1"
        ImagePath = "C:\ProgramData\AADMigration\Files\MigrationInProgress.bmp"
        RunOnceScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce2.ps1"
        RunOnceKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
        PowershellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe"
        ExecutionPolicy = "Unrestricted"
        RunOnceName = "NextRun"
        RebootAfterInstallation = $true
        Mode = "Prod"
    }
    PostRunOnce-Phase1EntraJoin @params
    Runs the migration process in Prod mode, executing all features, including user input blocking and reboots.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$MigrationConfigPath,

        [Parameter(Mandatory = $true)]
        [string]$ImagePath,

        [Parameter(Mandatory = $true)]
        [string]$RunOnceScriptPath,

        [Parameter(Mandatory = $true)]
        [string]$RunOnceKey,

        [Parameter(Mandatory = $true)]
        [string]$PowershellPath,

        [Parameter(Mandatory = $true)]
        [string]$ExecutionPolicy,

        [Parameter(Mandatory = $true)]
        [string]$RunOnceName,

        [Parameter(Mandatory = $false)]
        [switch]$RebootAfterInstallation,

        [Parameter(Mandatory = $false)]
        [ValidateSet("Dev", "Prod")]
        [string]$Mode
    )

    Begin {
        Write-EnhancedLog -Message "Starting PostRunOnce-Phase1EntraJoin function in $Mode mode" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Load the migration configuration
            Write-EnhancedLog -Message "Loading migration configuration from $MigrationConfigPath" -Level "INFO"
            $MigrationConfig = Import-PowerShellDataFile -Path $MigrationConfigPath
            $PPKGPath = $MigrationConfig.ProvisioningPack
            $MigrationPath = $MigrationConfig.MigrationPath
            Write-EnhancedLog -Message "Loaded PPKGPath: $PPKGPath, MigrationPath: $MigrationPath" -Level "INFO"

            # Block user input if in Prod mode
            if ($Mode -eq "Prod") {
                Write-EnhancedLog -Message "Blocking user input" -Level "INFO"
                $blockParams = @{
                    Block = $true
                }
                Block-UserInput @blockParams
                Write-EnhancedLog -Message "User input blocked" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Skipping user input blocking in Dev mode" -Level "WARNING"
            }

            # Show migration in progress form
            if ($Mode -eq "Prod") {
                Write-EnhancedLog -Message "Displaying migration in progress form with image $ImagePath" -Level "INFO"
                $formParams = @{
                    ImagePath = $ImagePath
                }
                Show-MigrationInProgressForm @formParams
                Write-EnhancedLog -Message "Migration in progress form displayed" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Skipping Displaying Migration in Progress form in Dev mode" -Level "WARNING"
            }

            # Set RunOnce script
            Write-EnhancedLog -Message "Setting RunOnce script at $RunOnceKey with script $RunOnceScriptPath" -Level "INFO"
            $runOnceParams = @{
                ScriptPath      = $RunOnceScriptPath
                RunOnceKey      = $RunOnceKey
                PowershellPath  = $PowershellPath
                ExecutionPolicy = $ExecutionPolicy
                RunOnceName     = $RunOnceName
            }
            Set-RunOnce @runOnceParams
            Write-EnhancedLog -Message "RunOnce script set" -Level "INFO"

            # Install provisioning package
            Write-EnhancedLog -Message "Installing provisioning package $PPKGPath from $MigrationPath" -Level "INFO"
            $installParams = @{
                PPKGPath      = $PPKGPath
                # MigrationPath = $MigrationPath
            }
            Install-PPKG @installParams
            Write-EnhancedLog -Message "Provisioning package installation command executed" -Level "INFO"

            # Unblock user input and close form if in Prod mode
            if ($Mode -eq "Prod") {
                Write-EnhancedLog -Message "Unblocking user input and closing migration progress form" -Level "INFO"
                Block-UserInput -Block $false
            }
            else {
                Write-EnhancedLog -Message "Skipping unblocking of user input in Dev mode" -Level "WARNING"
            }

            # Optionally reboot the machine
            if ($RebootAfterInstallation -and $Mode -eq "Prod") {
                Write-EnhancedLog -Message "Rebooting computer after successful migration" -Level "INFO"
                Restart-Computer
            }
            else {
                Write-EnhancedLog -Message "Skipping reboot as per mode or user request" -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred during the migration process: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting PostRunOnce-Phase1EntraJoin function" -Level "Notice"
    }
}
#EndRegion '.\Public\PostRunOnce-Phase1EntraJoin.ps1' 186
#Region '.\Public\PostRunOnce-Phase2EscrowBitlocker.ps1' -1

function PostRunOnce-Phase2EscrowBitlocker {
    <#
    .SYNOPSIS
    Executes post-run operations for the second phase of the migration process.
 
    .DESCRIPTION
    The PostRunOnce-Phase2EscrowBitlocker function blocks user input, displays a migration in progress form, creates a scheduled task for post-migration cleanup, escrows the BitLocker recovery key, sets various registry values, and optionally restarts the computer. The actions performed vary depending on the specified mode (Dev or Prod).
 
    .PARAMETER ImagePath
    The path to the image file to be displayed on the migration progress form.
 
    .PARAMETER TaskPath
    The path of the task in Task Scheduler.
 
    .PARAMETER TaskName
    The name of the scheduled task.
 
    .PARAMETER BitlockerDrives
    An array of drive letters for the BitLocker protected drives. The BitLocker recovery key for each drive will be escrowed as part of the process.
 
    .PARAMETER RegistrySettings
    A hashtable of registry settings to be applied. The hashtable should be structured with registry paths as keys, and each path should contain another hashtable with the registry value name, type, and data.
 
    .PARAMETER RebootAfterCompletion
    A switch parameter that controls whether the computer should be restarted after completing all post-run operations. If not specified, the computer will be restarted by default.
 
    .PARAMETER Mode
    Specifies the mode in which the script should run. Options are "Dev" for development mode or "Prod" for production mode. In Dev mode, certain features are skipped, while in Prod mode, all features are executed.
 
    .EXAMPLE
   $RegistrySettings = @(
        @{
            RegValName = "AutoAdminLogon"
            RegValType = "DWORD"
            RegValData = "0"
            RegKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
        },
        @{
            RegValName = "dontdisplaylastusername"
            RegValType = "DWORD"
            RegValData = "1"
            RegKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
        },
        @{
            RegValName = "legalnoticecaption"
            RegValType = "String"
            RegValData = "Migration Completed"
            RegKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
        },
        @{
            RegValName = "legalnoticetext"
            RegValType = "String"
            RegValData = "This PC has been migrated to Azure Active Directory. Please log in to Windows using your email address and password."
            RegKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
        }
    )
     
     
    $PostRunOncePhase2EscrowBitlockerParams = @{
        ImagePath = "C:\ProgramData\AADMigration\Files\MigrationInProgress.bmp"
        TaskPath = "AAD Migration"
        TaskName = "Run Post migration cleanup"
        BitlockerDrives = @("C:")
        RegistrySettings = $RegistrySettings # Correctly assign the array here
        Mode = $mode
    }
    PostRunOnce-Phase2EscrowBitlocker $PostRunOncePhase2EscrowBitlockerParams
    Executes the post-run operations in Dev mode, skipping user input blocking and reboots.
 
    .EXAMPLE
    $params = @{
        ImagePath = "C:\ProgramData\AADMigration\Files\MigrationInProgress.bmp"
        TaskPath = "AAD Migration"
        TaskName = "Run Post-migration cleanup"
        ScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce3.ps1"
        BitlockerDrives = @("C:", "D:")
        RegistrySettings = @{
            "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" = @{
                "AutoAdminLogon" = @{
                    "Type" = "DWORD"
                    "Data" = "0"
                }
            }
            "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" = @{
                "dontdisplaylastusername" = @{
                    "Type" = "DWORD"
                    "Data" = "1"
                }
                "legalnoticecaption" = @{
                    "Type" = "String"
                    "Data" = "Migration Completed"
                }
                "legalnoticetext" = @{
                    "Type" = "String"
                    "Data" = "This PC has been migrated to Azure Active Directory. Please log in to Windows using your email address and password."
                }
            }
        }
        Mode = "Prod"
    }
    PostRunOnce-Phase2EscrowBitlocker @params
    Executes the post-run operations in Prod mode, including user input blocking and reboots.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ImagePath,

        [Parameter(Mandatory = $true)]
        [string]$TaskPath,

        [Parameter(Mandatory = $true)]
        [string]$TaskName,

        [Parameter(Mandatory = $true)]
        [string[]]$BitlockerDrives,

        [Parameter(Mandatory = $true)]
        [hashtable[]]$RegistrySettings,

        # [Parameter(Mandatory = $true)]
        # $RegKeyPath,

        [Parameter(Mandatory = $false)]
        [switch]$RebootAfterCompletion,

        [Parameter(Mandatory = $false)]
        [ValidateSet("Dev", "Prod")]
        [string]$Mode = "Prod"
    )

    Begin {
        Write-EnhancedLog -Message "Starting PostRunOnce-Phase2EscrowBitlocker function in $Mode mode" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Block user input if in Prod mode
            if ($Mode -eq "Prod") {
                Write-EnhancedLog -Message "Blocking user input" -Level "INFO"
                $blockParams = @{
                    Block = $true
                }
                Block-UserInput @blockParams
                Write-EnhancedLog -Message "User input blocked" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Skipping user input blocking in Dev mode" -Level "WARNING"
            }

        
            # Show migration in progress form
            if ($Mode -eq "Prod") {
                Write-EnhancedLog -Message "Displaying migration in progress form with image $ImagePath" -Level "INFO"
                $formParams = @{
                    ImagePath = $ImagePath
                }
                Show-MigrationInProgressForm @formParams
                Write-EnhancedLog -Message "Migration in progress form displayed" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Skipping Displaying Migration in Progress form in Dev mode" -Level "WARNING"
            }


            # Create scheduled task for post-migration cleanup
            Write-EnhancedLog -Message "Creating scheduled task $TaskName at $TaskPath" -Level "INFO"

            # Define the parameters to be splatted
            $CreatePostMigrationCleanupTaskParams = @{
                TaskPath            = "AAD Migration"
                TaskName            = "Run Post migration cleanup"
                ScriptDirectory     = "C:\ProgramData\AADMigration\Scripts"
                ScriptName          = "ExecuteMigrationCleanupTasks.Task.ps1"
                TaskArguments       = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"{ScriptPath}`""
                TaskPrincipalUserId = "NT AUTHORITY\SYSTEM"
                TaskRunLevel        = "Highest"
                PowerShellPath      = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
                TaskDescription     = "Run post AAD Migration cleanup"
                TaskTriggerType     = "AtLogOn"  # Trigger type as a parameter
                Delay               = "PT1M"  # Optional delay before starting, set to 1 minute
            }

            # Call the function with the splatted parameters
            Create-PostMigrationCleanupTask @CreatePostMigrationCleanupTaskParams

            Write-EnhancedLog -Message "Scheduled task $TaskName created" -Level "INFO"

            $DBG

            # Escrow BitLocker recovery key for each drive
            foreach ($drive in $BitlockerDrives) {
                Write-EnhancedLog -Message "Escrowing BitLocker key for drive $drive" -Level "INFO"
                $escrowParams = @{
                    DriveLetter = $drive
                }
                Escrow-BitLockerKey @escrowParams
                Write-EnhancedLog -Message "BitLocker key for drive $drive escrowed" -Level "INFO"
            }



            #Set registry values
            
            # Iterate through each registry setting
            # foreach ($regSetting in $RegistrySettings) {
            # $regKeyPath = $regSetting.RegKeyPath

            # # Apply the registry setting
            # Apply-RegistrySettings -RegistrySettings @($regSetting) -RegKeyPath $regKeyPath
            # }


            # Group the registry settings by their RegKeyPath
            # $groupedRegistrySettings = $RegistrySettings | Group-Object -Property RegKeyPath

            # # Iterate through each group and apply the settings
            # foreach ($group in $groupedRegistrySettings) {
            # $regKeyPath = $group.Name
            # $settingsForKey = $group.Group

            # # Apply the registry settings for each path with all settings at once
            # Apply-RegistrySettings -RegistrySettings $settingsForKey -RegKeyPath $regKeyPath
            # }




            # Create a new hashtable to store settings grouped by their RegKeyPath
            $groupedSettings = @{}

            # Group the registry settings by their RegKeyPath
            foreach ($regSetting in $RegistrySettings) {
                $regKeyPath = $regSetting.RegKeyPath

                if (-not $groupedSettings.ContainsKey($regKeyPath)) {
                    $groupedSettings[$regKeyPath] = @()
                }

                # Add the current setting to the appropriate group
                $groupedSettings[$regKeyPath] += $regSetting
            }

            # Now apply the grouped registry settings
            foreach ($regKeyPath in $groupedSettings.Keys) {
                $settingsForKey = $groupedSettings[$regKeyPath]

                # Call Apply-RegistrySettings once per group with the correct RegKeyPath
                Apply-RegistrySettings -RegistrySettings $settingsForKey -RegKeyPath $regKeyPath
            }


            # Apply-RegistrySettings -RegistrySettings $RegistrySettings -RegKeyPath $RegKeyPath


            # Unblock user input and close form if in Prod mode
            if ($Mode -eq "Prod") {
                Write-EnhancedLog -Message "Unblocking user input and closing migration progress form" -Level "INFO"
                Block-UserInput -Block $false
            }
            else {
                Write-EnhancedLog -Message "Skipping unblocking of user input in Dev mode" -Level "WARNING"
            }

            # Optionally reboot the machine
            if ($RebootAfterCompletion -and $Mode -eq "Prod") {
                Write-EnhancedLog -Message "Rebooting computer after successful completion" -Level "INFO"
                Restart-Computer
            }
            else {
                Write-EnhancedLog -Message "Skipping reboot as per mode or user request" -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in PostRunOnce-Phase2EscrowBitlocker function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting PostRunOnce-Phase2EscrowBitlocker function" -Level "Notice"
    }
}
#EndRegion '.\Public\PostRunOnce-Phase2EscrowBitlocker.ps1' 286
#Region '.\Public\Prepare-AADMigration.ps1' -1

function Prepare-AADMigration {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$MigrationPath,

        [Parameter(Mandatory = $true)]
        [string]$PSScriptbase,

        [Parameter(Mandatory = $true)]
        [string]$ConfigBaseDirectory,

        [Parameter(Mandatory = $true)]
        [string]$ConfigFileName,

        [Parameter(Mandatory = $true)]
        [string]$TenantID,

        [Parameter(Mandatory = $true)]
        [bool]$OneDriveKFM,

        [Parameter(Mandatory = $true)]
        [bool]$InstallOneDrive
    )

    Begin {
        Write-EnhancedLog -Message "Starting Prepare-AADMigration function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Ensure the target directory exists
            if (-not (Test-Path -Path $MigrationPath)) {
                New-Item -Path $MigrationPath -ItemType Directory -Force | Out-Null
                Write-EnhancedLog -Message "New destination directory created: $MigrationPath" -Level "INFO"
            }

            # Copy the entire content of $PSScriptRoot to $MigrationPath

            Stop-ProcessesUsingOneDriveLib -OneDriveLibPath "C:\ProgramData\AADMigration\Files\OneDriveLib.dll"

            # $DBG

            # Remove the ADD migration
            Remove-ScheduledTaskFilesWithLogging -Path $MigrationPath

            # Copy-FilesToPathWithKill -SourcePath $sourcePath1 -DestinationPath $destinationPath1

            # Ensure the destination directory exists
            # if (Test-Path -Path $MigrationPath) {
            # Write-EnhancedLog -Message "Destination directory already exists. Removing: $MigrationPath" -Level "WARNING"
            # Remove-Item -Path $MigrationPath -Recurse -Force
            # Write-EnhancedLog -Message "Destination directory removed: $MigrationPath" -Level "INFO"
            # }

            # Create a new destination directory
            # New-Item -Path $MigrationPath -ItemType Directory | Out-Null


            if (-not (Test-Path -Path $MigrationPath)) {
                New-Item -Path $MigrationPath -ItemType Directory -Force | Out-Null
                Write-EnhancedLog -Message "New destination directory created: $MigrationPath" -Level "INFO"
            }
            


            $params = @{
                Source          = $PSScriptbase
                Destination     = $MigrationPath
                Exclude         = ".git"
                RetryCount      = 2
                WaitTime        = 5
                RequiredSpaceGB = 10
            }


            # Execute the function with splatting
            Copy-FilesWithRobocopy @params

            # Verify the copy operation for $PSScriptRoot
            Verify-CopyOperation -SourcePath $PSScriptbase -DestinationPath $MigrationPath
            ####################################################################################
            # Import migration configuration
            $MigrationConfig = Import-LocalizedData -BaseDirectory $ConfigBaseDirectory -FileName $ConfigFileName
            $TenantID = $MigrationConfig.TenantID
            $OneDriveKFM = $MigrationConfig.UseOneDriveKFM
            $InstallOneDrive = $MigrationConfig.InstallOneDrive

            # $DBG

            # Set OneDrive KFM settings if required
            if ($OneDriveKFM) {

                # $TenantID = "YourTenantID"
                $RegistrySettings = @(
                    @{
                        RegValName = "AllowTenantList"
                        RegValType = "STRING"
                        RegValData = $TenantID
                    },
                    @{
                        RegValName = "SilentAccountConfig"
                        RegValType = "DWORD"
                        RegValData = "1"
                    },
                    @{
                        RegValName = "KFMOptInWithWizard"
                        RegValType = "STRING"
                        RegValData = $TenantID
                    },
                    @{
                        RegValName = "KFMSilentOptIn"
                        RegValType = "STRING"
                        RegValData = $TenantID
                    },
                    @{
                        RegValName = "KFMSilentOptInDesktop"
                        RegValType = "DWORD"
                        RegValData = "1"
                    },
                    @{
                        RegValName = "KFMSilentOptInDocuments"
                        RegValType = "DWORD"
                        RegValData = "1"
                    },
                    @{
                        RegValName = "KFMSilentOptInPictures"
                        RegValType = "DWORD"
                        RegValData = "1"
                    }
                )
                
                $SetODKFMRegistrySettingsParams = @{
                    TenantID         = $TenantID
                    RegKeyPath       = "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive"
                    RegistrySettings = $RegistrySettings
                }
                
                Set-ODKFMRegistrySettings @SetODKFMRegistrySettingsParams

            }

            # Install OneDrive if required
            if ($InstallOneDrive) {
                

                # Example usage
                $installParams = @{
                    MigrationPath            = "C:\ProgramData\AADMigration"
                    SoftwareName             = "OneDrive"
                    SetupUri                 = "https://go.microsoft.com/fwlink/?linkid=844652"
                    SetupFile                = "OneDriveSetup.exe"
                    RegKey                   = "HKLM:\SOFTWARE\Microsoft\OneDrive"
                    MinVersion               = [version]"24.146.0721.0003"
                    ExePath                  = "C:\Program Files\Microsoft OneDrive\OneDrive.exe"
                    ScheduledTaskName        = "OneDriveRemediation"
                    ScheduledTaskDescription = "Restart OneDrive to kick off KFM sync"
                    SetupArgumentList        = "/allusers"
                    KFM                      = $true
                    TimestampPrefix          = "OneDriveSetup_"
                }
                
                Install-Software @installParams
            }

            # # # Example usage with splatting
            # $CreateOneDriveSyncUtilStatusTask = @{
            # TaskPath = "AAD Migration"
            # TaskName = "AADM Get OneDrive Sync Util Status"
            # ScriptDirectory = "C:\ProgramData\AADMigration\Scripts"
            # ScriptName = "Check-ODSyncUtilStatus.Task.ps1"
            # TaskArguments = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
            # TaskRepetitionDuration = "P1D"
            # TaskRepetitionInterval = "PT30M"
            # TaskPrincipalGroupId = "BUILTIN\Users"
            # PowerShellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
            # TaskDescription = "AADM Get OneDrive Sync Util Status"
            # AtLogOn = $true
            # }

            # Create-OneDriveSyncUtilStatusTask @CreateOneDriveSyncUtilStatusTask



            $CreateOneDriveSyncUtilStatusTask = @{
                TaskPath               = "AAD Migration"
                TaskName               = "AADM Get OneDrive Sync Util Status"
                ScriptDirectory        = "C:\ProgramData\AADMigration\Scripts"
                ScriptName             = "Check-ODSyncUtilStatus.Task.ps1"
                TaskArguments          = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
                TaskRepetitionDuration = "P1D"
                TaskRepetitionInterval = "PT30M"
                PowerShellPath         = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
                TaskDescription        = "Get current OneDrive Sync Status and write to event log"
                AtLogOn                = $true
                # UseCurrentUser = $true # Specify to use the current user
                TaskPrincipalGroupId   = "BUILTIN\Users"
            }
            
            Create-OneDriveSyncUtilStatusTask @CreateOneDriveSyncUtilStatusTask
            



            $RemoveExistingStatusFilesParams = @{
                LogFolder      = "logs"
                StatusFileName = "ODSyncUtilStatus.json"
        
            }
            # Remove existing status files
            Remove-ExistingStatusFiles @RemoveExistingStatusFilesParams


            $taskParams = @{
                TaskPath = "AAD Migration"
                TaskName = "AADM Get OneDrive Sync Util Status"
            }

            # Trigger OneDrive Sync Status Scheduled Task
            Trigger-ScheduledTask @taskParams

            # Example usage with try-catch mechanism and Write-EnhancedLog
            $AnalyzeOneDriveSyncUtilStatusParams = @{
                LogFolder      = "logs"
                StatusFileName = "ODSyncUtilStatus.json"
                MaxRetries     = 5
                RetryInterval  = 10
            }

            try {
                $result = Analyze-OneDriveSyncUtilStatus @AnalyzeOneDriveSyncUtilStatusParams

                # Example decision-making based on the result
                if ($result.Status -eq "Healthy") {
                    Write-EnhancedLog -Message "OneDrive is healthy, no further action required." -Level "INFO"
                }
                elseif ($result.Status -eq "InProgress") {
                    Write-EnhancedLog -Message "OneDrive is syncing, please wait..." -Level "INFO"
                }
                elseif ($result.Status -eq "Failed") {
                    Write-EnhancedLog -Message "OneDrive has encountered an error, please investigate." -Level "WARNING"
                }
                else {
                    Write-EnhancedLog -Message "OneDrive status is unknown, further analysis required." -Level "NOTICE"
                }
            }
            catch {
                Write-EnhancedLog -Message "An error occurred while analyzing OneDrive status: $($_.Exception.Message)" -Level "ERROR"
                Write-EnhancedLog -Message "Please check if you are logged in to OneDrive and try again." -Level "ERROR"
                Handle-Error -ErrorRecord $_
    
                # Throw to halt the entire script
                throw $_
            }


            #Todo now we have OneDrive installed and running we need to actually start using our OneDrive for Business location on the local machine to copy user specific files into it as part of our On-prem AD to Entra ID migration prep so we need to copy the following PR4B projects from before

            # 1- copy Outlook Signatures
            # 2- copy Downloads folders
            # any other user specific files


            # $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
            # Write-Host "Current User: $currentUser"

            # $CreateUserFileBackupTaskParams = @{
            # TaskPath = "AAD Migration"
            # TaskName = "User File Backup to OneDrive"
            # ScriptDirectory = "C:\ProgramData\AADMigration\Scripts"
            # ScriptName = "BackupUserFiles.Task.ps1"
            # TaskArguments = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
            # TaskRepetitionDuration = "P1D"
            # TaskRepetitionInterval = "PT30M"
            # TaskPrincipalGroupId = "BUILTIN\Users"
            # PowerShellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
            # TaskDescription = "User File Backup to OneDrive"
            # AtLogOn = $true
            # }
            
            # Create-UserFileBackupTask @CreateUserFileBackupTaskParams



            $CreateUserFileBackupTaskParams = @{
                TaskPath               = "AAD Migration"
                TaskName               = "User File Backup to OneDrive"
                ScriptDirectory        = "C:\ProgramData\AADMigration\Scripts"
                ScriptName             = "BackupUserFiles.Task.ps1"
                TaskArguments          = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
                TaskRepetitionDuration = "P1D"
                TaskRepetitionInterval = "PT30M"
                PowerShellPath         = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
                TaskDescription        = "User File Backup to OneDrive"
                AtLogOn                = $true
                # UseCurrentUser = $true # Switch to use the current user

                TaskPrincipalGroupId   = "BUILTIN\Users"

            }
            
            Create-UserFileBackupTask @CreateUserFileBackupTaskParams
            





            $RemoveExistingStatusFilesParams = @{
                LogFolder      = "logs"
                StatusFileName = "UserFilesBackupStatus.json"
            }
            # Remove existing status files
            Remove-ExistingStatusFiles @RemoveExistingStatusFilesParams


            $TriggerScheduledTaskParams = @{
                TaskPath = "AAD Migration"
                TaskName = "User File Backup to OneDrive"
            }

            # Call the function with splatting
            Trigger-ScheduledTask @TriggerScheduledTaskParams

            # Define the parameters for splatting
            $AnalyzeParams = @{
                LogFolder      = "logs"
                StatusFileName = "UserFilesBackupStatus.json"
                MaxRetries     = 5
                RetryInterval  = 10
            }

            # Call the Analyze-CopyOperationStatus function using splatting
            Analyze-CopyOperationStatus @AnalyzeParams

        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Prepare-AADMigration: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Prepare-AADMigration function" -Level "Notice"
    }
}

# # Define parameters
# $PrepareAADMigrationParams = @{
# MigrationPath = "C:\ProgramData\AADMigration"
# PSScriptRoot = "C:\SourcePath"
# ConfigBaseDirectory = "C:\ConfigDirectory\Scripts"
# ConfigFileName = "MigrationConfig.psd1"
# TenantID = "YourTenantID"
# OneDriveKFM = $true
# InstallOneDrive = $true
# }

# # Example usage with splatting
# Prepare-AADMigration @PrepareAADMigrationParams
#EndRegion '.\Public\Prepare-AADMigration.ps1' 363
#Region '.\Public\Prepare-SolutionDirectory.ps1' -1

function Prepare-SolutionDirectory {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ToolkitFolder,

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

    Begin {
        Write-EnhancedLog -Message "Starting Prepare-SolutionDirectory function" -Level "INFO"
        Log-Params -Params @{
            ToolkitFolder = $ToolkitFolder
            FilesFolder = $FilesFolder
        }
    }

    Process {
        try {
            # Create necessary directories
            New-Item -ItemType Directory -Path $ToolkitFolder -Force
            New-Item -ItemType Directory -Path $FilesFolder -Force
        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Prepare-SolutionDirectory function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Prepare-SolutionDirectory function" -Level "INFO"
    }
}

# Example usage
# Prepare-SolutionDirectory -ToolkitFolder "C:\path\to\toolkit" -FilesFolder "C:\path\to\files"
#EndRegion '.\Public\Prepare-SolutionDirectory.ps1' 37
#Region '.\Public\Remove-AADMigrationArtifacts.ps1' -1

function Remove-AADMigrationArtifacts {
    <#
    .SYNOPSIS
    Cleans up AAD migration artifacts, including directories, scheduled tasks, and a local user.
 
    .DESCRIPTION
    The `Remove-AADMigrationArtifacts` function removes the following AAD migration-related artifacts:
    - The `C:\logs` directory
    - The `C:\ProgramData\AADMigration` directory
    - All scheduled tasks under the `AAD Migration` task path
    - The `AAD Migration` scheduled task folder
    - A local user account named `MigrationInProgress`
 
    .EXAMPLE
    Remove-AADMigrationArtifacts
 
    Cleans up all AAD migration artifacts.
 
    .NOTES
    This function should be run with administrative privileges.
    #>


    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting AAD migration artifact cleanup..."
    }

    Process {
        # Define paths to clean up
        $pathsToClean = @(
            @{ Path = "C:\logs"; Name = "Logs Path" },
            @{ Path = "C:\ProgramData\AADMigration"; Name = "AADMigration Path" },
            @{ Path = "C:\temp"; Name = "AADMigration Secrets Path" },
            @{ Path = "C:\temp-logs"; Name = "Temp Logs Path" }, # Added
            @{ Path = "C:\temp-git"; Name = "Temp Git Path" }, # Added
            @{ Path = "C:\temp-git\logs.zip"; Name = "Temp Zip File" }, # Added
            @{ Path = "C:\temp-git\syslog"; Name = "Syslog Repo Path" } # Added
        )

        # Loop through each path and perform the check and removal
        foreach ($item in $pathsToClean) {
            $path = $item.Path
            $name = $item.Name

            if (Test-Path -Path $path) {
                Write-EnhancedLog -Message "Removing $name ($path)..." -Level 'INFO'
                Remove-Item -Path $path -Recurse -Force
            }
            else {
                Write-EnhancedLog -Message "$name ($path) does not exist, skipping..." -Level 'WARNING'
            }
        }

        Write-EnhancedLog -Message "Path cleanup complete." -Level 'NOTICE'



        # Remove all scheduled tasks under the AAD Migration task path
        $scheduledTasks = Get-ScheduledTask -TaskPath '\AAD Migration\' -ErrorAction SilentlyContinue
        if ($scheduledTasks) {
            foreach ($task in $scheduledTasks) {
                Write-EnhancedLog -Message "Removing scheduled task: $($task.TaskName)..." -Level 'INFO'
                Unregister-ScheduledTask -TaskName $task.TaskName -TaskPath $task.TaskPath -Confirm:$false
            }
        }
        else {
            Write-EnhancedLog -Message "No scheduled tasks found under \AAD Migration, skipping..." -Level 'WARNING'
        }

        # Remove the scheduled task folder named AAD Migration
        try {
            $taskFolder = New-Object -ComObject "Schedule.Service"
            $taskFolder.Connect()
            $rootFolder = $taskFolder.GetFolder("\")
            $aadMigrationFolder = $rootFolder.GetFolder("AAD Migration")
            $aadMigrationFolder.DeleteFolder("", 0)
            Write-EnhancedLog -Message "Scheduled task folder AAD Migration removed successfully." -Level 'INFO'
        }
        catch {
            Write-EnhancedLog -Message "Scheduled task folder AAD Migration does not exist or could not be removed." -Level 'ERROR'
        }

        # Remove the local user called MigrationInProgress
        $localUser = "MigrationInProgress"
        try {
            $user = Get-LocalUser -Name $localUser -ErrorAction Stop
            if ($user) {
                Write-EnhancedLog -Message "Removing local user $localUser..." -Level 'INFO'
                Remove-LocalUser -Name $localUser -Force
            }
        }
        catch {
            Write-EnhancedLog -Message "Local user $localUser does not exist, skipping..." -Level 'WARNING'
        }
    }

    End {
        Write-EnhancedLog -Message "AAD migration artifact cleanup completed." -Level 'INFO'
    }
}

# Remove-AADMigrationArtifacts
#EndRegion '.\Public\Remove-AADMigrationArtifacts.ps1' 105
#Region '.\Public\Remove-ADJoin.ps1' -1

function Remove-ADJoin {
    <#
    .SYNOPSIS
        Removes the computer from the Active Directory domain.
     
    .DESCRIPTION
        This function checks if the computer is part of an Active Directory domain and, if so, removes it from the domain.
        It also disables specified scheduled tasks, manages network adapters, and restarts the computer if necessary.
     
    .PARAMETER DomainLeaveUser
        The domain user account to use for leaving the domain.
     
    .PARAMETER DomainLeavePassword
        The password for the domain user account.
     
    .PARAMETER TempUser
        The temporary local user to use if domain credentials fail.
     
    .PARAMETER TempUserPassword
        The password for the temporary local user.
     
    .PARAMETER ComputerName
        The name of the computer to remove from the domain.
     
    .PARAMETER TaskName
        The name of the scheduled task to be disabled.
     
    .PARAMETER TaskPath
        The path of the scheduled task to be disabled.
     
    .EXAMPLE
        Remove-ADJoin -DomainLeaveUser "AdminUser" -DomainLeavePassword "P@ssw0rd" -TempUser "LocalAdmin" -TempUserPassword "P@ssw0rd" -ComputerName "localhost" -TaskName "TaskName" -TaskPath "\Path\To\Task"
     
    .NOTES
        This function performs multiple actions, including removing the computer from the domain, disabling scheduled tasks, managing network adapters, and restarting the computer.
    #>

    [CmdletBinding()]
    param (
        [string]$DomainLeaveUser,
        [string]$DomainLeavePassword,
        [string]$TempUser,
        [string]$TempUserPassword,
        [string]$ComputerName
    )
    
    Begin {
        Write-EnhancedLog -Message "Starting Remove-ADJoin function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
    
    Process {
        try {
            $PartOfDomain = Check-DomainMembership
    
            if ($PartOfDomain) {
                Write-EnhancedLog -Message "Computer is domain member, removing domain membership" -Level "INFO"
                $leaveDomainParams = @{
                    DomainLeaveUser     = $DomainLeaveUser
                    DomainLeavePassword = $DomainLeavePassword
                    ComputerName        = $ComputerName
                    TempUser            = $TempUser
                    TempUserPassword    = $TempUserPassword
                }
                
                Leave-Domain @leaveDomainParams
                
    
                # Disable-ScheduledTaskByPath -TaskName $TaskName -TaskPath $TaskPath
    
                Manage-NetworkAdapters -Disable
                Start-Sleep -Seconds 5
                Manage-NetworkAdapters
    
                # Restart-ComputerIfNeeded
            }
            else {
                Write-EnhancedLog -Message "Computer is not a domain member, no domain removal needed." -Level "INFO"
                # Disable-ScheduledTaskByPath -TaskName $TaskName -TaskPath $TaskPath
                # Restart-ComputerIfNeeded
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while removing AD join: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }
    
    End {
        Write-EnhancedLog -Message "Exiting Remove-ADJoin function" -Level "NOTICE"
    }
}
#EndRegion '.\Public\Remove-ADJoin.ps1' 92
#Region '.\Public\Remove-CompanyPortal.ps1' -1

function Remove-CompanyPortal {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$AppxPackageName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Remove-CompanyPortal function" -Level "INFO"
        Log-Params -Params @{ AppxPackageName = $AppxPackageName }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Removing AppxPackage: $AppxPackageName" -Level "INFO"
            Get-AppxPackage -AllUsers -Name $AppxPackageName | Remove-AppxPackage -Confirm:$false
        } catch {
            Write-EnhancedLog -Message "An error occurred while removing AppxPackage: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-CompanyPortal function" -Level "INFO"
    }
}

# $RemoveCompanyPortalParams = @{
# AppxPackageName = "Microsoft.CompanyPortal"
# }

# Remove-CompanyPortal @RemoveCompanyPortalParams
#EndRegion '.\Public\Remove-CompanyPortal.ps1' 33
#Region '.\Public\Remove-DeviceCertificates.ps1' -1

# Function to remove device certificates
function Remove-DeviceCertificates {
    param (
        [string]$CertLocalMachinePath,
        [string[]]$DeviceCertIssuers
    )

    Write-EnhancedLog -Message "Attempting to remove device certificates with issuers: $DeviceCertIssuers" -Level "INFO"
    $DeviceCerts = Get-ChildItem -Path $CertLocalMachinePath -Recurse
    $IntuneCerts = $DeviceCerts | Where-Object { $DeviceCertIssuers -contains $_.Issuer }
    foreach ($Cert in $IntuneCerts) {
        Write-EnhancedLog -Message "Removing device certificate: $($Cert.Subject)" -Level "INFO"
        $Cert | Remove-Item -Force -ErrorAction SilentlyContinue
    }
    Write-EnhancedLog -Message "Device certificates removed successfully" -Level "INFO"

    return [PSCustomObject]@{
        Action = "Remove Device Certificates"
        Path   = $CertLocalMachinePath
        Status = "Success"
    }
}
#EndRegion '.\Public\Remove-DeviceCertificates.ps1' 23
#Region '.\Public\Remove-ExistingStatusFiles.ps1' -1

function Remove-ExistingStatusFiles {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$LogFolder,

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

    Begin {
        Write-EnhancedLog -Message "Starting Remove-ExistingStatusFiles function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
        $statusFiles = [System.Collections.Generic.List[System.IO.FileInfo]]::new()
        $isSystem = Test-RunningAsSystem
    }

    Process {
        if ($isSystem) {
            Write-EnhancedLog -Message "Running as SYSTEM. Analyzing logs across all user profiles." -Level "INFO"
            $userProfiles = Get-ChildItem 'C:\Users' -Directory | Where-Object { $_.Name -notlike "Public" -and $_.Name -notlike "Default*" }

            foreach ($profile in $userProfiles) {
                $profileLogFolder = Join-Path -Path $profile.FullName -ChildPath $LogFolder
                $profileStatusFile = Join-Path -Path $profileLogFolder -ChildPath $StatusFileName
                if (Test-Path -Path $profileStatusFile) {
                    $statusFiles.Add((Get-Item -Path $profileStatusFile))
                }
            }

            if ($statusFiles.Count -gt 0) {
                foreach ($file in $statusFiles) {
                    if (Test-Path -Path $file.FullName) {
                        $removeParams = @{
                            Path               = $file.FullName
                            ForceKillProcesses = $true
                            MaxRetries         = 5
                            RetryInterval      = 10
                        }
                        Remove-EnhancedItem @removeParams
                        Write-EnhancedLog -Message "Removed existing status file: $($file.FullName)" -Level "INFO"
                    }
                }
            }
            else {
                Write-EnhancedLog -Message "No status files found across user profiles." -Level "WARNING"
            }
        }
        else {
            #Not Running as SYSTEM but running As User so we will scan the current user profile instead of all user profiles
            $logFolder = Join-Path -Path $env:USERPROFILE -ChildPath $LogFolder
            $statusFile = Join-Path -Path $logFolder -ChildPath $StatusFileName

            if (Test-Path -Path $statusFile) {
                $removeParams = @{
                    Path               = $statusFile
                    ForceKillProcesses = $true
                    MaxRetries         = 5
                    RetryInterval      = 10
                }
                Remove-EnhancedItem @removeParams
                Write-EnhancedLog -Message "Removed existing status file: $statusFile" -Level "INFO"
            }
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-ExistingStatusFiles function" -Level "Notice"
    }
}
#EndRegion '.\Public\Remove-ExistingStatusFiles.ps1' 71
#Region '.\Public\Remove-Hybrid.ps1' -1

function Remove-Hybrid {
    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Remove-Hybrid function" -Level "INFO"
    }

    Process {
        try {
          
            # $Dsregcmd = New-Object PSObject
            # Dsregcmd /status | Where-Object { $_ -match ' : ' } | ForEach-Object {
            # $Item = $_.Trim() -split '\s:\s'
            # $Dsregcmd | Add-Member -MemberType NoteProperty -Name $($Item[0] -replace '[:\s]', '') -Value $Item[1] -ErrorAction SilentlyContinue
            # }

            # $AzureADJoined = $Dsregcmd.AzureAdJoined

            Write-EnhancedLog -Message "Checking if device is Hyrbid Azure AD joined" -Level "INFO"

            # Main script execution block
            $dsregStatus = Get-DSRegStatus

            # Determine and output the join status
            if ($dsregStatus.IsWorkgroup) {
                Write-EnhancedLog -Message "Device is Workgroup joined (not Azure AD, Hybrid, or On-prem Joined)."
            }
            elseif ($dsregStatus.IsAzureADJoined -and -not $dsregStatus.IsHybridJoined) {
                Write-EnhancedLog -Message "Device is Azure AD Joined."
            }
            elseif ($dsregStatus.IsHybridJoined) {
                Write-EnhancedLog -Message "Device is Hybrid Joined (both On-prem and Azure AD Joined)."
            }
            elseif ($dsregStatus.IsOnPremJoined) {
                Write-EnhancedLog -Message "Device is On-prem Joined only."
            }

            # Determine and output the MDM enrollment status
            if ($dsregStatus.IsMDMEnrolled) {
                Write-EnhancedLog -Message "Device is Intune Enrolled."
            }
            else {
                Write-EnhancedLog -Message "Device is NOT Intune Enrolled."
            }

            # Exit code based on Azure AD and MDM status
            if ($dsregStatus.IsAzureADJoined -and -not $dsregStatus.IsHybridJoined -and $dsregStatus.IsMDMEnrolled) {
                Write-EnhancedLog -Message "Device is Azure AD Joined and Intune Enrolled. No migration needed." -Level "INFO"
                # exit 0 # Do not migrate: Device is Azure AD Joined and Intune Enrolled
            }
            else {
                # Migrate: All other cases where the device is not 100% Azure AD joined or is hybrid/on-prem joined
                # exit 1
            }

            if ($dsregStatus.IsHybridJoined) {
                Write-EnhancedLog -Message "Device is Hybrid Azure AD joined. Removing hybrid join." -Level "INFO"
                & "C:\Windows\System32\dsregcmd.exe" /leave
            }

        
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while removing hybrid join: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-Hybrid function" -Level "INFO"
    }
}

# Example usage
# Remove-Hybrid
#EndRegion '.\Public\Remove-Hybrid.ps1' 77
#Region '.\Public\Remove-InstalledPPKG.ps1' -1

function Remove-InstalledPPKG {
    <#
    .SYNOPSIS
    Removes an installed provisioning package by its PackageName.
 
    .DESCRIPTION
    This function checks if a provisioning package is installed based on the provided package name.
    If the package is installed, it retrieves the PackageId and removes it from the system, with detailed
    logging and error handling.
 
    .PARAMETER PackageName
    The name of the provisioning package to remove.
 
    .EXAMPLE
    Remove-InstalledPPKG -PackageName "MyProvisioningPackage"
    # Removes the provisioning package with the name "MyProvisioningPackage".
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Specify the name of the provisioning package to remove.")]
        [ValidateNotNullOrEmpty()]
        [string]$PackageName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Remove-InstalledPPKG function for package: $PackageName" -Level "INFO"
    }

    Process {
        try {
            # Write-EnhancedLog -Message "Provisioning package $PackageName is already installed, attempting to remove it." -Level "WARNING"

            # Log the attempt to fetch installed package information
            Write-EnhancedLog -Message "Retrieving installed package information for package: $PackageName" -Level "INFO"

            # Retrieve the correct PackageId from the installed package information
            $installedPackage = Get-ProvisioningPackage -AllInstalledPackages | Where-Object { $_.PackageName -like "*$PackageName*" }

            # Check if the installed package information is found
            if ($installedPackage) {
                $installedPackageId = $installedPackage.PackageId
                Write-EnhancedLog -Message "Found installed package ID: $installedPackageId" -Level "INFO"

                try {
                    # Log the attempt to remove the package
                    Write-EnhancedLog -Message "Attempting to remove provisioning package with ID: $installedPackageId" -Level "INFO"

                    # Remove the installed provisioning package using PackageId
                    Remove-ProvisioningPackage -PackageID $installedPackageId

                    # Log success of removal
                    Write-EnhancedLog -Message "Provisioning package removed successfully with ID: $installedPackageId" -Level "INFO"
                }
                catch {
                    # Log error during package removal
                    Write-EnhancedLog -Message "Error during provisioning package removal using ID $installedPackageId $($_.Exception.Message)" -Level "ERROR"
                    Handle-Error -ErrorRecord $_
                    throw $_
                }
            }
            else {
                # Log if the installed package information could not be found
                Write-EnhancedLog -Message "Error: Unable to find the installed provisioning package with name: $PackageName" -Level "ERROR"
                throw "Provisioning package information not found for removal."
            }
        }
        catch {
            # General error handling for the entire removal process
            Write-EnhancedLog -Message "An error occurred while trying to remove the installed provisioning package: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-InstalledPPKG function for package: $PackageName" -Level "INFO"
    }
}

# Example usage:
# Remove-InstalledPPKG -PackageName "ICTC_Project_2"
#EndRegion '.\Public\Remove-InstalledPPKG.ps1' 83
#Region '.\Public\Remove-IntuneMgmt.ps1' -1

# Main function to remove Intune management
function Remove-IntuneMgmt {
    [CmdletBinding()]
    param (
        [string]$OMADMPath,
        [string]$EnrollmentBasePath,
        [string]$TrackedBasePath,
        [string]$PolicyManagerBasePath,
        [string]$ProvisioningBasePath,
        [string]$CertCurrentUserPath,
        [string]$CertLocalMachinePath,
        [string]$TaskPathBase,
        [string]$MSDMProviderID,
        [string[]]$RegistryPathsToRemove,
        [string]$UserCertIssuer,
        [string[]]$DeviceCertIssuers
    )

    Begin {
        # Initialize counters and summary table
        $successCount = 0
        $warningCount = 0
        $errorCount = 0
        $summaryTable = [System.Collections.Generic.List[PSCustomObject]]::new()

        Write-EnhancedLog -Message "Starting Remove-IntuneMgmt function" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Check Intune enrollment
            $Account = Check-IntuneEnrollment -OMADMPath $OMADMPath -EnrollmentBasePath $EnrollmentBasePath -MSDMProviderID $MSDMProviderID
            if ($null -eq $Account) {
                Write-EnhancedLog -Message "Device is not enrolled in Intune. No further action required." -Level "INFO"
                $warningCount++
                return
            }

            # Perform cleanup tasks
            $summaryTable.Add((Remove-ScheduledTasks -TaskPathBase $TaskPathBase -Account $Account))
            $summaryTable.Add((Remove-RegistryKeys -RegistryPathsToRemove $RegistryPathsToRemove -Account $Account))
            $summaryTable.Add((Remove-UserCertificates -CertCurrentUserPath $CertCurrentUserPath -UserCertIssuer $UserCertIssuer))
            $summaryTable.Add((Remove-DeviceCertificates -CertLocalMachinePath $CertLocalMachinePath -DeviceCertIssuers $DeviceCertIssuers))
            $successCount++
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while removing Intune management: $($_.Exception.Message)" -Level "ERROR"
            $errorCount++
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        # Generate final summary report
        # Define a hashtable for splatting
        $reportParams = @{
            successCount = $successCount
            warningCount = $warningCount
            errorCount   = $errorCount
            summaryTable = $summaryTable
        }

        # Call the function using the splat
        Generate-RemoveIntuneMgmtSummaryReport @reportParams


        Write-EnhancedLog -Message "Exiting Remove-IntuneMgmt function" -Level "INFO"
    }
}


# $RemoveIntuneMgmtParams = @{
# OMADMPath = "HKLM:\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts\*"
# EnrollmentBasePath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments"
# TrackedBasePath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EnterpriseResourceManager\Tracked"
# PolicyManagerBasePath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager"
# ProvisioningBasePath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning"
# CertCurrentUserPath = "cert:\CurrentUser"
# CertLocalMachinePath = "cert:\LocalMachine"
# TaskPathBase = "\Microsoft\Windows\EnterpriseMgmt"
# MSDMProviderID = "MS DM Server"
# RegistryPathsToRemove = @(
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments\Status",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EnterpriseResourceManager\Tracked",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\AdmxInstalled",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\Providers",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\OMADM\Logger",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\OMADM\Sessions"
# )
# UserCertIssuer = "CN=SC_Online_Issuing"
# DeviceCertIssuers = @("CN=Microsoft Intune Root Certification Authority", "CN=Microsoft Intune MDM Device CA")
# }

# Remove-IntuneMgmt @RemoveIntuneMgmtParams
#EndRegion '.\Public\Remove-IntuneMgmt.ps1' 98
#Region '.\Public\Remove-LocalUserAccount.ps1' -1

function Remove-LocalUserAccount {
    <#
    .SYNOPSIS
    Removes a specified local user account.
 
    .DESCRIPTION
    The Remove-LocalUserAccount function removes a specified local user account if it exists.
 
    .PARAMETER UserName
    The name of the local user account to be removed.
 
    .EXAMPLE
    $params = @{
        UserName = "MigrationInProgress"
    }
    Remove-LocalUserAccount @params
    Removes the local user account named "MigrationInProgress".
    #>


    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $true)]
        [string]$UserName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Remove-LocalUserAccount function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Check if running in PowerShell 7 or later and import LocalAccounts
        if ($PSVersionTable.PSVersion.Major -ge 7) {
            Write-EnhancedLog -Message "Running in PowerShell 7. Importing LocalAccounts module using Windows PowerShell..." -Level "INFO"
            try {
                Import-Module -Name Microsoft.PowerShell.LocalAccounts -UseWindowsPowerShell -ErrorAction Stop
            }
            catch {
                Write-EnhancedLog -Message "Failed to import LocalAccounts module: $($_.Exception.Message)" -Level "ERROR"
                throw
            }
        }
    }

    Process {
        try {
            # Check if the user account exists
            $user = Get-LocalUser -Name $UserName -ErrorAction SilentlyContinue

            if ($null -eq $user) {
                Write-EnhancedLog -Message "User account '$UserName' does not exist. No action taken." -Level "WARNING"
            }
            else {
                if ($PSCmdlet.ShouldProcess("User Account", "Removing user account '$UserName'")) {
                    Write-EnhancedLog -Message "Removing local user account: $UserName" -Level "INFO"
                    Remove-LocalUser -Name $UserName -ErrorAction Stop
                    Write-EnhancedLog -Message "Successfully removed local user account: $UserName" -Level "INFO"
                }
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Remove-LocalUserAccount function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-LocalUserAccount function" -Level "Notice"
    }
}

# # Example usage
# $params = @{
# UserName = "MigrationInProgress"
# }
# Remove-LocalUserAccount @params
#EndRegion '.\Public\Remove-LocalUserAccount.ps1' 76
#Region '.\Public\Remove-MigrationFiles.ps1' -1

function Remove-MigrationFiles {
    <#
    .SYNOPSIS
    Removes specified directories used during the migration process.
   
    .DESCRIPTION
    The Remove-MigrationFiles function deletes specified directories used during the migration process, leaving the log folder intact.
   
    .PARAMETER Directories
    An array of directories to be removed.
   
    .EXAMPLE
    $params = @{
        Directories = @(
            "C:\ProgramData\AADMigration\Files",
            "C:\ProgramData\AADMigration\Scripts",
            "C:\ProgramData\AADMigration\Toolkit"
        )
    }
    Remove-MigrationFiles @params
    Removes the specified directories.
    #>

  
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Directories
    )
  
    Begin {
        Write-EnhancedLog -Message "Starting Remove-MigrationFiles function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
  
    Process {
        try {
            foreach ($directory in $Directories) {
                if (Test-Path -Path $directory) {
                    Write-EnhancedLog -Message "Removing directory: $directory" -Level "INFO"
                    $removeParams = @{
                        Path               = $directory
                        ForceKillProcesses = $true
                        MaxRetries         = 5
                        RetryInterval      = 10
                    }
                    Remove-EnhancedItem @removeParams
                    Write-EnhancedLog -Message "Successfully removed directory: $directory" -Level "INFO"
                }
                else {
                    Write-EnhancedLog -Message "Directory not found: $directory" -Level "WARNING"
                }
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Remove-MigrationFiles function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
  
    End {
        Write-EnhancedLog -Message "Exiting Remove-MigrationFiles function" -Level "Notice"
    }
}


# Example usage
# $params = @{
# Directories = @(
# "C:\ProgramData\AADMigration\Files",
# "C:\ProgramData\AADMigration\Scripts",
# "C:\ProgramData\AADMigration\Toolkit"
# )
# }
# Remove-MigrationFiles @params
#EndRegion '.\Public\Remove-MigrationFiles.ps1' 77
#Region '.\Public\Remove-OrphanedSIDs-Archive.ps1' -1


# function Remove-OrphanedSIDs {
# <#
# .SYNOPSIS
# Removes orphaned SIDs from the specified group.

# .DESCRIPTION
# The Remove-OrphanedSIDs function checks for orphaned SIDs in a specified group, typically 'Administrators'. It attempts to remove any orphaned SIDs found and logs the process.

# .PARAMETER GroupName
# The name of the group from which orphaned SIDs should be removed. Defaults to "Administrators".

# .EXAMPLE
# Remove-OrphanedSIDs -GroupName "Administrators"
# Removes orphaned SIDs from the Administrators group.
# #>

# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $false, HelpMessage = "Provide the group name to remove orphaned SIDs from.")]
# [string]$GroupName = "Administrators"
# )

# Begin {
# Write-EnhancedLog -Message "Starting Remove-OrphanedSIDs function" -Level "Notice"
# Write-EnhancedLog -Message "Checking for orphaned SIDs in the '$GroupName' group." -Level "INFO"

# # Get orphaned SIDs using the previous function
# $orphanedSIDs = Get-OrphanedSIDs -GroupName $GroupName

# # Check if any orphaned SIDs were found
# if ($orphanedSIDs.Count -eq 0) {
# Write-EnhancedLog -Message "No orphaned SIDs found in the '$GroupName' group." -Level "INFO"
# } else {
# Write-EnhancedLog -Message "Found $($orphanedSIDs.Count) orphaned SIDs in the '$GroupName' group." -Level "INFO"
# }
# }

# Process {
# # Proceed if orphaned SIDs were found
# if ($orphanedSIDs.Count -gt 0) {
# foreach ($orphanedSID in $orphanedSIDs) {
# try {
# # Log the attempt to remove the SID
# Write-EnhancedLog -Message "Attempting to remove orphaned SID: $($orphanedSID.AccountName) from '$GroupName'." -Level "INFO"

# # Remove the orphaned SID from the group
# Remove-LocalGroupMember -Group $GroupName -Member $orphanedSID.AccountName -ErrorAction Stop

# # Log successful removal
# Write-EnhancedLog -Message "Successfully removed orphaned SID: $($orphanedSID.AccountName)." -Level "INFO"
# } catch {
# # Log any errors during removal
# Write-EnhancedLog -Message "Failed to remove orphaned SID: $($orphanedSID.AccountName). Error: $($_.Exception.Message)" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# }
# }
# }
# }

# End {
# Write-EnhancedLog -Message "Exiting Remove-OrphanedSIDs function" -Level "Notice"
# }
# }

# # Example usage
# # Remove-OrphanedSIDs -GroupName "Administrators"
#EndRegion '.\Public\Remove-OrphanedSIDs-Archive.ps1' 68
#Region '.\Public\Remove-OrphanedSIDsFromAdministratorsGroup-Archive.ps1' -1

# function Remove-OrphanedSIDsFromAdministratorsGroup {
# <#
# .SYNOPSIS
# Removes orphaned SIDs from the local "Administrators" group.

# .DESCRIPTION
# This function iterates through the members of the local "Administrators" group and removes any orphaned SIDs, which are typically leftover accounts no longer associated with a user.

# .EXAMPLE
# Remove-OrphanedSIDsFromAdministratorsGroup

# This will check the local "Administrators" group for any orphaned SIDs and remove them.

# .NOTES
# Author: Abdullah Ollivierre
# Date: 2024-09-06

# #>

# # Access the local "Administrators" group using ADSI
# $group = [ADSI]"WinNT://./Administrators,group"

# # Retrieve the members of the group
# $members = $group.psbase.Invoke("Members")

# # Iterate through each member of the group
# foreach ($member in $members) {
# try {
# # Try to resolve the member (if this works, the member is valid)
# $memberPath = $member.GetType().InvokeMember('AdsPath', 'GetProperty', $null, $member, $null)

# # Check if the member is an orphaned SID by checking the format of the AdsPath
# if ($memberPath -like "*S-1-*") {
# Write-Host "Orphaned SID detected: $memberPath" -ForegroundColor Yellow

# # Extract just the SID from the path and remove it
# $orphanedSID = $memberPath -replace "^.*?S-1-", "S-1-"
# try {
# $group.Remove("WinNT://$orphanedSID")
# Write-Host "Successfully removed orphaned SID: $orphanedSID" -ForegroundColor Green
# }
# catch {
# Write-Host "Failed to remove orphaned SID: $($_.Exception.Message)" -ForegroundColor Red
# }
# }
# else {
# Write-Host "Valid member: $memberPath"
# }
# }
# catch {
# # If we cannot resolve the member, it is an orphaned SID
# Write-Host "Failed to resolve member, skipping." -ForegroundColor Red
# }
# }
# }


# # Remove-OrphanedSIDsFromAdministratorsGroup
#EndRegion '.\Public\Remove-OrphanedSIDsFromAdministratorsGroup-Archive.ps1' 59
#Region '.\Public\Remove-RegistryKeys.ps1' -1


# Function to remove registry keys
function Remove-RegistryKeys {
    param (
        [string[]]$RegistryPathsToRemove,
        [string]$Account
    )

    Write-EnhancedLog -Message "Attempting to remove registry keys associated with enrollment" -Level "INFO"
    foreach ($RegistryPath in $RegistryPathsToRemove) {
        Write-EnhancedLog -Message "Removing registry path: $RegistryPath\$Account" -Level "INFO"
        Remove-Item -Path "$RegistryPath\$Account" -Recurse -Force -ErrorAction SilentlyContinue
    }
    Write-EnhancedLog -Message "Registry keys removed successfully (if any existed)" -Level "INFO"

    return [PSCustomObject]@{
        Action = "Remove Registry Keys"
        Path   = "$RegistryPath\$Account"
        Status = "Success"
    }
}
#EndRegion '.\Public\Remove-RegistryKeys.ps1' 22
#Region '.\Public\Remove-ScheduledTasks.ps1' -1


# Function to remove scheduled tasks
function Remove-ScheduledTasks {
    param (
        [string]$TaskPathBase,
        [string]$Account
    )

    Write-EnhancedLog -Message "Attempting to remove scheduled tasks at path $TaskPathBase\$Account" -Level "INFO"
    Get-ScheduledTask -TaskPath "$TaskPathBase\$Account\*" | Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue
    Write-EnhancedLog -Message "Scheduled tasks removed successfully (if any existed)" -Level "INFO"

    return [PSCustomObject]@{
        Action = "Remove Scheduled Tasks"
        Path   = "$TaskPathBase\$Account"
        Status = "Success"
    }
}
#EndRegion '.\Public\Remove-ScheduledTasks.ps1' 19
#Region '.\Public\Remove-UserCertificates.ps1' -1


# Function to remove user certificates
function Remove-UserCertificates {
    param (
        [string]$CertCurrentUserPath,
        [string]$UserCertIssuer
    )

    Write-EnhancedLog -Message "Attempting to remove user certificates with issuer: $UserCertIssuer" -Level "INFO"
    $UserCerts = Get-ChildItem -Path $CertCurrentUserPath -Recurse
    $IntuneCerts = $UserCerts | Where-Object { $_.Issuer -eq $UserCertIssuer }
    foreach ($Cert in $IntuneCerts) {
        Write-EnhancedLog -Message "Removing certificate: $($Cert.Subject)" -Level "INFO"
        $Cert | Remove-Item -Force
    }
    Write-EnhancedLog -Message "User certificates removed successfully" -Level "INFO"

    return [PSCustomObject]@{
        Action = "Remove User Certificates"
        Path   = $CertCurrentUserPath
        Status = "Success"
    }
}
#EndRegion '.\Public\Remove-UserCertificates.ps1' 24
#Region '.\Public\Replace-BannerImage.ps1' -1

function Replace-BannerImage {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Source,

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

    Begin {
        Write-EnhancedLog -Message "Starting Replace-BannerImage function" -Level "INFO"
        Log-Params -Params @{
            Source = $Source
            Destination = $Destination
        }
    }

    Process {
        try {
            # Replace the banner image in the toolkit folder
            Copy-Item -Path $Source -Destination $Destination -Force
        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Replace-BannerImage function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Replace-BannerImage function" -Level "INFO"
    }
}

# Example usage
# Replace-BannerImage -Source 'C:\YourPath\YourBannerImage.png' -Destination 'C:\YourPath\Toolkit\AppDeployToolkit\AppDeployToolkitBanner.png'
#EndRegion '.\Public\Replace-BannerImage.ps1' 36
#Region '.\Public\Replace-DeployApplicationPS1.ps1' -1

function Replace-DeployApplicationPS1 {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Source,

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

    Begin {
        Write-EnhancedLog -Message "Starting Replace-DeployApplicationPS1 function" -Level "INFO"
        Log-Params -Params @{
            Source = $Source
            Destination = $Destination
        }
    }

    Process {
        try {
            # Replace Deploy-Application.ps1 in the toolkit folder
            Copy-Item -Path $Source -Destination $Destination -Force
        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Replace-DeployApplicationPS1 function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Replace-DeployApplicationPS1 function" -Level "INFO"
    }
}

# Example usage
# Replace-DeployApplicationPS1 -Source 'C:\YourPath\Scripts\Deploy-Application.ps1' -Destination 'C:\YourPath\Toolkit\Deploy-Application.ps1'
#EndRegion '.\Public\Replace-DeployApplicationPS1.ps1' 36
#Region '.\Public\Resolve-SID-Archive.ps1' -1

# function Resolve-SID {
# param (
# [string]$AccountName
# )

# try {
# $account = Get-WmiObject -Class Win32_UserAccount -Filter "Name='$AccountName'"
# if ($account) {
# # Use the SID directly
# $sid = New-Object System.Security.Principal.SecurityIdentifier($account.SID)
# return $sid
# } else {
# Write-EnhancedLog -Message "Unable to resolve SID for $AccountName." -Level "WARNING"
# return $null
# }
# }
# catch {
# Write-EnhancedLog -Message "Error resolving SID for $AccountName $_" -Level "ERROR"
# return $null
# }
# }
#EndRegion '.\Public\Resolve-SID-Archive.ps1' 22
#Region '.\Public\Restart-ComputerIfNeeded.ps1' -1

function Restart-ComputerIfNeeded {
    <#
    .SYNOPSIS
    Restarts the computer if necessary.
 
    .DESCRIPTION
    This function forces a restart of the computer and logs the process. It is typically used after significant system changes that require a reboot.
 
    .EXAMPLE
    Restart-ComputerIfNeeded
 
    Forces a computer restart and logs the process.
 
    .NOTES
    This function should be used with caution as it forces an immediate restart.
    #>


    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Restart-ComputerIfNeeded function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            Write-EnhancedLog -Message "Restarting computer..." -Level "INFO"
            Restart-Computer -Force
        }
        catch {
            Write-EnhancedLog -Message "An error occurred during the restart process: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            Write-EnhancedLog -Message "Exiting Restart-ComputerIfNeeded function" -Level "NOTICE"
        }
    }

    End {
        Write-EnhancedLog -Message "Restart-ComputerIfNeeded function completed" -Level "INFO"
    }
}
#EndRegion '.\Public\Restart-ComputerIfNeeded.ps1' 45
#Region '.\Public\Set-Autologin.ps1' -1

function Set-Autologin {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TempUser,

        [Parameter(Mandatory = $true)]
        [string]$TempUserPassword,

        [Parameter(Mandatory = $true)]
        [string]$RegPath ,

        [Parameter(Mandatory = $true)]
        [string]$AutoAdminLogonName ,

        [Parameter(Mandatory = $true)]
        [string]$AutoAdminLogonValue ,

        [Parameter(Mandatory = $true)]
        [string]$DefaultUsernameName,

        [Parameter(Mandatory = $true)]
        [string]$DefaultPasswordName ,

        [Parameter(Mandatory = $false)]
        [string]$DefaultDomainName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Set-Autologin function" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            Write-EnhancedLog -Message "Setting user account $TempUser to Auto Login" -Level "INFO"

            $autoLoginParams = @{
                Path  = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
                Name  = "AutoAdminLogon"
                Value = "1"
            }

            if (Test-Path -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\AutoAdminLogon") {
                Remove-ItemProperty @autoLoginParams
            }

            # Set AutoAdminLogon
            Set-ItemProperty -Path $RegPath -Name $AutoAdminLogonName -Value $AutoAdminLogonValue -Type String -Verbose

            # Set DefaultUserName
            Set-ItemProperty -Path $RegPath -Name $DefaultUsernameName -Value $TempUser -Type String -Verbose

            # Set DefaultPassword
            Set-ItemProperty -Path $RegPath -Name $DefaultPasswordName -Value $TempUserPassword -Type String -Verbose

            # Set DefaultDomainName if provided
            if ($PSBoundParameters.ContainsKey('DefaultDomainName')) {
                Set-ItemProperty -Path $RegPath -Name 'DefaultDomainName' -Value $DefaultDomainName -Type String -Verbose
            }

            # Create UserList key if it doesn't exist and add the user
            $userListPath = "$RegPath\SpecialAccounts\UserList"
            if (-not (Test-Path -Path $userListPath)) {
                Write-EnhancedLog -Message "Creating UserList registry path: $userListPath" -Level "INFO"
                New-Item -Path $userListPath -Force
            }
            New-ItemProperty -Path $userListPath -Name $TempUser -Value 0 -PropertyType DWord -Force -Verbose

            Write-EnhancedLog -Message "Auto-login set for user '$TempUser'." -Level 'INFO'
        } catch {
            Write-EnhancedLog -Message "An error occurred while setting autologin: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Set-Autologin function" -Level "INFO"
    }
}

# # Example usage with splatting
# $SetAutologinParams = @{
# TempUser = 'YourTempUser'
# TempUserPassword = 'YourTempUserPassword'
# RegPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
# AutoAdminLogonName = 'AutoAdminLogon'
# AutoAdminLogonValue = '1'
# DefaultUsernameName = 'DefaultUserName'
# DefaultPasswordName = 'DefaultPassword'
# DefaultDomainName = $env:COMPUTERNAME
# }

# Set-Autologin @SetAutologinParams
#EndRegion '.\Public\Set-Autologin.ps1' 95
#Region '.\Public\Set-ODKFMRegistrySettings.ps1' -1

function Set-ODKFMRegistrySettings {
    <#
    .SYNOPSIS
    Sets OneDrive Known Folder Move (KFM) registry settings.
 
    .DESCRIPTION
    The Set-ODKFMRegistrySettings function sets specified registry values for OneDrive Known Folder Move (KFM) based on provided tenant ID, registry key path, and an array of registry settings.
 
    .PARAMETER TenantID
    The tenant ID for OneDrive.
 
    .PARAMETER RegKeyPath
    The path to the registry key.
 
    .PARAMETER RegistrySettings
    An array of registry settings to be applied. Each setting should include RegValName, RegValType, and RegValData.
 
    .EXAMPLE
    $settings = @(
        @{
            RegValName = "KFMValue1"
            RegValType = "String"
            RegValData = "Value1"
        },
        @{
            RegValName = "KFMValue2"
            RegValType = "DWORD"
            RegValData = "1"
        }
    )
    $params = @{
        TenantID = "your-tenant-id"
        RegKeyPath = "HKLM:\Software\Policies\Microsoft\OneDrive"
        RegistrySettings = $settings
    }
    Set-ODKFMRegistrySettings @params
    Sets the specified OneDrive KFM registry values.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TenantID,

        [Parameter(Mandatory = $true)]
        [string]$RegKeyPath,

        [Parameter(Mandatory = $true)]
        $RegistrySettings
    )

    Begin {
        Write-EnhancedLog -Message "Starting Set-ODKFMRegistrySettings function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {

            # Apply the registry settings using the defined hash table
            Apply-RegistrySettings -RegistrySettings $RegistrySettings -RegKeyPath $RegKeyPath

        }
        catch {
            Write-EnhancedLog -Message "An error occurred while setting OneDrive KFM registry values: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Set-ODKFMRegistrySettings function" -Level "Notice"
    }
}


# # Example usage
# $settings = @(
# @{
# RegValueName = "KFMValue1"
# RegValType = "String"
# RegValData = "Value1"
# },
# @{
# RegValueName = "KFMValue2"
# RegValType = "DWORD"
# RegValData = "1"
# }
# )
# $params = @{
# TenantID = "your-tenant-id"
# RegKeyPath = "HKLM:\Software\Policies\Microsoft\OneDrive"
# RegistrySettings = $settings
# }
# Set-ODKFMRegistrySettings @params


# $TenantID = "YourTenantID"

# $RegistrySettings = @(
# @{
# RegValueName = "AllowTenantList"
# RegValType = "STRING"
# RegValData = $TenantID
# },
# @{
# RegValueName = "SilentAccountConfig"
# RegValType = "DWORD"
# RegValData = "1"
# },
# @{
# RegValueName = "KFMOptInWithWizard"
# RegValType = "STRING"
# RegValData = $TenantID
# },
# @{
# RegValueName = "KFMSilentOptIn"
# RegValType = "STRING"
# RegValData = $TenantID
# },
# @{
# RegValueName = "KFMSilentOptInDesktop"
# RegValType = "DWORD"
# RegValData = "1"
# },
# @{
# RegValueName = "KFMSilentOptInDocuments"
# RegValType = "DWORD"
# RegValData = "1"
# },
# @{
# RegValueName = "KFMSilentOptInPictures"
# RegValType = "DWORD"
# RegValData = "1"
# }
# )

# $SetODKFMRegistrySettingsParams = @{
# TenantID = $TenantID
# RegKeyPath = "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive"
# RegistrySettings = $RegistrySettings
# }

# Set-ODKFMRegistrySettings @SetODKFMRegistrySettingsParams


#optionally you can create an event source here using Create-EventLogSource.ps1
#EndRegion '.\Public\Set-ODKFMRegistrySettings.ps1' 147
#Region '.\Public\Set-RegistryValue.ps1' -1

function Set-RegistryValue {
    <#
    .SYNOPSIS
    Sets a registry value with validation before and after setting the value.
 
    .DESCRIPTION
    The Set-RegistryValue function sets a registry value at a specified registry path. It validates the value before setting it and ensures it is set correctly after the operation.
 
    .PARAMETER RegKeyPath
    The path to the registry key.
 
    .PARAMETER RegValName
    The name of the registry value.
 
    .PARAMETER RegValType
    The type of the registry value (e.g., String, DWORD).
 
    .PARAMETER RegValData
    The data to be set for the registry value. It can be a string, an integer, or even an empty string.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$RegKeyPath,

        [Parameter(Mandatory = $true)]
        [string]$RegValName,

        [Parameter(Mandatory = $true)]
        [string]$RegValType,

        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$RegValData
    )

    Begin {
        Write-EnhancedLog -Message "Starting Set-RegistryValue function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        CheckAndElevate
    }

    Process {
        try {
            # Validate before setting the value
            Write-EnhancedLog -Message "Validating registry value before setting" -Level "INFO"
            $validationBefore = Validate-RegistryValue -RegKeyPath $RegKeyPath -RegValName $RegValName -ExpectedValData $RegValData

            if ($validationBefore) {
                Write-EnhancedLog -Message "Registry value $RegValName is already correctly set. No action taken." -Level "INFO"
                return $true
            }

            # Test to see if registry key exists, if it does not exist create it
            if (-not (Test-Path -Path $RegKeyPath)) {
                New-Item -Path $RegKeyPath -Force | Out-Null
                Write-EnhancedLog -Message "Created registry key: $RegKeyPath" -Level "INFO"
            }

            # Check if value exists and if it needs updating
            try {
                $CurrentValue = Get-ItemPropertyValue -Path $RegKeyPath -Name $RegValName
            }
            catch {
                # If value does not exist, create it
                Set-ItemProperty -Path $RegKeyPath -Name $RegValName -Type $RegValType -Value $RegValData -Force
                Write-EnhancedLog -Message "Created registry value: $RegValName with data: $RegValData" -Level "INFO"
                
                # Validate after setting the value
                $validationAfter = Validate-RegistryValue -RegKeyPath $RegKeyPath -RegValName $RegValName -ExpectedValData $RegValData
                if ($validationAfter) {
                    return $true
                }
                else {
                    return $false
                }
            }

            if ($CurrentValue -ne $RegValData) {
                # If value exists but data is wrong, update the value
                Set-ItemProperty -Path $RegKeyPath -Name $RegValName -Type $RegValType -Value $RegValData -Force
                Write-EnhancedLog -Message "Updated registry value: $RegValName with data: $RegValData" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Registry value: $RegValName already has the correct data: $RegValData" -Level "INFO"
            }

            # Validate after setting the value
            $validationAfter = Validate-RegistryValue -RegKeyPath $RegKeyPath -RegValName $RegValName -ExpectedValData $RegValData
            if ($validationAfter) {
                return $true
            }
            else {
                return $false
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Set-RegistryValue function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            return $false
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Set-RegistryValue function" -Level "Notice"
    }
}




# # Define your parameters
# $regKeyPath = "HKCU:\Software\MyApp"
# $regValName = "Setting"
# $regValType = "String"
# $regValData = "Enabled"

# # Call the Set-RegistryValue function and capture the result
# $setRegistryResult = Set-RegistryValue -RegKeyPath $regKeyPath -RegValName $regValName -RegValType $regValType -RegValData $regValData

# # Build decision-making logic based on the result
# if ($setRegistryResult -eq $true) {
# Write-EnhancedLog -Message "Successfully set the registry value: $regValName at $regKeyPath" -Level "INFO"
# } else {
# Write-EnhancedLog -Message "Failed to set the registry value: $regValName at $regKeyPath" -Level "ERROR"
# }


#EndRegion '.\Public\Set-RegistryValue.ps1' 131
#Region '.\Public\Set-RunOnce.ps1' -1

# function Set-RunOnce {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true)]
# [string]$ScriptPath,
        
# [Parameter(Mandatory = $true)]
# [string]$RunOnceKey,
        
# [Parameter(Mandatory = $true)]
# [string]$PowershellPath,
        
# [Parameter(Mandatory = $true)]
# [string]$ExecutionPolicy,
        
# [Parameter(Mandatory = $true)]
# [string]$RunOnceName
# )

# Begin {
# Write-EnhancedLog -Message "Starting Set-RunOnce function" -Level "INFO"
# Log-Params -Params @{
# ScriptPath = $ScriptPath
# RunOnceKey = $RunOnceKey
# PowershellPath = $PowershellPath
# ExecutionPolicy = $ExecutionPolicy
# RunOnceName = $RunOnceName
# }
# }

# Process {
# try {
# Write-EnhancedLog -Message "Setting RunOnce script" -Level "INFO"
# $RunOnceValue = "$PowershellPath -executionPolicy $ExecutionPolicy -File $ScriptPath"
# Set-ItemProperty -Path $RunOnceKey -Name $RunOnceName -Value $RunOnceValue -Verbose
# } catch {
# Write-EnhancedLog -Message "An error occurred while setting RunOnce script: $($_.Exception.Message)" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# }
# }

# End {
# Write-EnhancedLog -Message "Exiting Set-RunOnce function" -Level "INFO"
# }
# }

# # # Example usage with splatting
# # $SetRunOnceParams = @{
# # ScriptPath = "C:\YourScriptPath.ps1"
# # RunOnceKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
# # PowershellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe"
# # ExecutionPolicy = "Unrestricted"
# # RunOnceName = "NextRun"
# # }

# # Set-RunOnce @SetRunOnceParams







function Set-RunOnce {
    <#
    .SYNOPSIS
    Sets a RunOnce registry key to execute a specified script on the next system startup.
 
    .DESCRIPTION
    The Set-RunOnce function sets a RunOnce registry key to execute a specified PowerShell script on the next system startup. This can be useful for scheduling post-reboot tasks.
 
    .PARAMETER ScriptPath
    The path to the PowerShell script to be executed on the next system startup.
 
    .PARAMETER RunOnceKey
    The registry key path for the RunOnce entry.
 
    .PARAMETER PowershellPath
    The path to the PowerShell executable.
 
    .PARAMETER ExecutionPolicy
    The execution policy for running the PowerShell script.
 
    .PARAMETER RunOnceName
    The name of the RunOnce entry.
 
    .EXAMPLE
    $params = @{
        ScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce2.ps1"
        RunOnceKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
        PowershellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe"
        ExecutionPolicy = "Unrestricted"
        RunOnceName = "NextRun"
    }
    Set-RunOnce @params
    Sets the RunOnce registry key to execute the specified script on the next system startup.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ScriptPath,
        
        [Parameter(Mandatory = $true)]
        [string]$RunOnceKey,
        
        [Parameter(Mandatory = $true)]
        [string]$PowershellPath,
        
        [Parameter(Mandatory = $true)]
        [string]$ExecutionPolicy,
        
        [Parameter(Mandatory = $true)]
        [string]$RunOnceName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Set-RunOnce function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
        # Log-Params -Params @{
        # ScriptPath = $ScriptPath
        # RunOnceKey = $RunOnceKey
        # PowershellPath = $PowershellPath
        # ExecutionPolicy = $ExecutionPolicy
        # RunOnceName = $RunOnceName
        # }
    }

    Process {
        try {
            # Validate script path
            if (-not (Test-Path -Path $ScriptPath)) {
                Throw "Script file not found: $ScriptPath"
            }

            Write-EnhancedLog -Message "Setting RunOnce registry key for script: $ScriptPath" -Level "INFO"
            $RunOnceValue = "$PowershellPath -executionPolicy $ExecutionPolicy -File $ScriptPath"

            $params = @{
                Path  = $RunOnceKey
                Name  = $RunOnceName
                Value = $RunOnceValue
            }

            Set-ItemProperty @params
            Write-EnhancedLog -Message "RunOnce registry key set successfully." -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Set-RunOnce function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Set-RunOnce function" -Level "Notice"
    }
}

# # Example usage with splatting
# $params = @{
# ScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce2.ps1"
# RunOnceKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
# PowershellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe"
# ExecutionPolicy = "Unrestricted"
# RunOnceName = "NextRun"
# }
# Set-RunOnce @params
#EndRegion '.\Public\Set-RunOnce.ps1' 169
#Region '.\Public\Show-MigrationInProgressForm.ps1' -1

function Show-MigrationInProgressForm {
    <#
    .SYNOPSIS
    Displays a migration in progress form.
 
    .DESCRIPTION
    The Show-MigrationInProgressForm function displays a form with a "Migration in Progress" message and an image to indicate that a migration process is ongoing. The form is displayed in full-screen mode and prevents user interaction with other windows.
 
    .PARAMETER ImagePath
    The path to the image file to be displayed on the form.
 
    .EXAMPLE
    $params = @{
        ImagePath = "C:\ProgramData\AADMigration\Files\MigrationInProgress.bmp"
    }
    Show-MigrationInProgressForm @params
    Displays the migration in progress form with the specified image.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ImagePath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Show-MigrationInProgressForm function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            if (-not (Test-Path -Path $ImagePath)) {
                Throw "Image file not found: $ImagePath"
            }

            [void][reflection.assembly]::LoadWithPartialName("System.Drawing")
            [void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
            $img = [System.Drawing.Image]::FromFile($ImagePath)

            [System.Windows.Forms.Application]::EnableVisualStyles()
            $form = New-Object Windows.Forms.Form
            $form.Text = "Migration in Progress"
            $form.WindowState = 'Maximized'
            $form.BackColor = "#000000"
            $form.TopMost = $true

            $pictureBox = New-Object Windows.Forms.PictureBox
            $pictureBox.Width = $img.Size.Width
            $pictureBox.Height = $img.Size.Height
            $pictureBox.Dock = "Fill"
            $pictureBox.SizeMode = "StretchImage"
            $pictureBox.Image = $img
            $form.Controls.Add($pictureBox)
            $form.Add_Shown({ $form.Activate() })
            $form.Show()
            Write-EnhancedLog -Message "Displayed migration in progress form." -Level "INFO"

            # Keep the form open
            # while ($form.Visible) {
            # [System.Windows.Forms.Application]::DoEvents()
            # }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Show-MigrationInProgressForm function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Show-MigrationInProgressForm function" -Level "Notice"
    }
}

# # Example usage
# $params = @{
# ImagePath = "C:\ProgramData\AADMigration\Files\MigrationInProgress.bmp"
# }
# Show-MigrationInProgressForm @params
#EndRegion '.\Public\Show-MigrationInProgressForm.ps1' 81
#Region '.\Public\Start-FileDownloadWithRetry.ps1' -1

function Start-FileDownloadWithRetry {

    <#
    .SYNOPSIS
        Downloads a file from a specified URL with retry logic. Falls back to using WebClient if BITS transfer fails.
 
    .DESCRIPTION
        This function attempts to download a file from a specified source URL to a destination path using BITS (Background Intelligent Transfer Service).
        If BITS fails after a specified number of retries, the function falls back to using the .NET WebClient class for the download.
 
    .PARAMETER Source
        The URL of the file to download.
 
    .PARAMETER Destination
        The file path where the downloaded file will be saved.
 
    .PARAMETER MaxRetries
        The maximum number of retry attempts if the download fails. Default is 3.
 
    .EXAMPLE
        Start-FileDownloadWithRetry -Source "https://example.com/file.zip" -Destination "C:\Temp\file.zip"
 
    .NOTES
        Author: Abdullah Ollivierre
        Date: 2024-08-15
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Source,

        [Parameter(Mandatory = $true)]
        [string]$Destination,

        [Parameter(Mandatory = $false)]
        [int]$MaxRetries = 3
    )

    Begin {
        Write-EnhancedLog -Message "Starting Start-FileDownloadWithRetry function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Ensure the destination folder exists, create it if necessary
        $destinationFolder = Split-Path -Path $Destination -Parent
        if (-not (Test-Path -Path $destinationFolder)) {
            Write-EnhancedLog -Message "Destination folder does not exist. Creating folder: $destinationFolder" -Level "INFO"
            New-Item -Path $destinationFolder -ItemType Directory | Out-Null
        }
    }

    Process {
        $attempt = 0
        $success = $false

        while ($attempt -lt $MaxRetries -and -not $success) {
            try {
                $attempt++
                Write-EnhancedLog -Message "Attempt $attempt to download from $Source to $Destination" -Level "INFO"

                if (-not (Test-Path -Path $destinationFolder)) {
                    throw "Destination folder does not exist: $destinationFolder"
                }

                # Attempt download using BITS
                $bitsTransferParams = @{
                    Source      = $Source
                    Destination = $Destination
                    ErrorAction = "Stop"
                }
                Start-BitsTransfer @bitsTransferParams

                # Validate file existence and size after download
                if (Test-Path $Destination) {
                    $fileInfo = Get-Item $Destination
                    if ($fileInfo.Length -gt 0) {
                        Write-EnhancedLog -Message "Download successful using BITS on attempt $attempt. File size: $($fileInfo.Length) bytes" -Level "INFO"
                        $success = $true
                    }
                    else {
                        Write-EnhancedLog -Message "Download failed: File is empty after BITS transfer." -Level "ERROR"
                        throw "Download failed due to empty file after BITS transfer."
                    }
                }
                else {
                    Write-EnhancedLog -Message "Download failed: File not found after BITS transfer." -Level "ERROR"
                    throw "Download failed due to missing file after BITS transfer."
                }

            }
            catch {
                Write-EnhancedLog -Message "BITS transfer failed on attempt $attempt $($_.Exception.Message)" -Level "ERROR"
                if ($attempt -eq $MaxRetries) {
                    Write-EnhancedLog -Message "Maximum retry attempts reached. Falling back to WebClient for download." -Level "WARNING"
                    try {
                        $webClient = [System.Net.WebClient]::new()
                        $webClient.DownloadFile($Source, $Destination)
                    
                        # Validate file existence and size after download
                        if (Test-Path $Destination) {
                            $fileInfo = Get-Item $Destination
                            if ($fileInfo.Length -gt 0) {
                                Write-EnhancedLog -Message "Download successful using WebClient. File size: $($fileInfo.Length) bytes" -Level "INFO"
                                $success = $true
                            }
                            else {
                                Write-EnhancedLog -Message "Download failed: File is empty after WebClient download." -Level "ERROR"
                                throw "Download failed due to empty file after WebClient download."
                            }
                        }
                        else {
                            Write-EnhancedLog -Message "Download failed: File not found after WebClient download." -Level "ERROR"
                            throw "Download failed due to missing file after WebClient download."
                        }
                    }
                    catch {
                        Write-EnhancedLog -Message "WebClient download failed: $($_.Exception.Message)" -Level "ERROR"
                        throw "Download failed after multiple attempts using both BITS and WebClient."
                    }
                    
                }
                else {
                    Start-Sleep -Seconds 5
                }
            }
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Start-FileDownloadWithRetry function" -Level "NOTICE"
    }
}

# # Generate a timestamped folder within the TEMP directory
# $timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss")
# $destinationFolder = [System.IO.Path]::Combine($env:TEMP, "OneDriveSetup_$timestamp")

# # Set up the parameters for downloading OneDrive Setup
# $downloadParams = @{
# Source = "https://go.microsoft.com/fwlink/?linkid=844652" # OneDrive Setup URL
# Destination = [System.IO.Path]::Combine($destinationFolder, "OneDriveSetup.exe") # Local destination path in the timestamped folder
# MaxRetries = 3 # Number of retry attempts
# }

# # Call the Start-FileDownloadWithRetry function with splatted parameters
# Start-FileDownloadWithRetry @downloadParams
#EndRegion '.\Public\Start-FileDownloadWithRetry.ps1' 147
#Region '.\Public\Stop-ProcessesUsingOneDriveLib.ps1' -1

function Stop-ProcessesUsingOneDriveLib {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$OneDriveLibPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Stop-ProcessesUsingOneDriveLib function" -Level "INFO"
        Log-Params -Params @{ OneDriveLibPath = $OneDriveLibPath }
    }

    Process {
        try {
            # Validate before removal
            $initialProcesses = Validate-OneDriveLibUsage -OneDriveLibPath $OneDriveLibPath
            if ($initialProcesses.Count -eq 0) {
                Write-EnhancedLog -Message "No processes found using OneDriveLib.dll before attempting termination." -Level "INFO"
            }

            # Terminate processes
            foreach ($process in $initialProcesses) {
                Write-EnhancedLog -Message "Found process using OneDriveLib.dll: $($process.ProcessName) (ID: $($process.ProcessId)). Attempting to terminate." -Level "WARNING"
                Stop-Process -Id $process.ProcessId -Force -ErrorAction Stop
            }

            # Validate after removal
            $remainingProcesses = Validate-OneDriveLibUsage -OneDriveLibPath $OneDriveLibPath
            if ($remainingProcesses.Count -eq 0) {
                Write-EnhancedLog -Message "Successfully terminated all processes using OneDriveLib.dll." -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Some processes could not be terminated. Manual intervention may be required." -Level "ERROR"
                foreach ($process in $remainingProcesses) {
                    Write-EnhancedLog -Message "Process still running: $($process.ProcessName) (ID: $($process.ProcessId))." -Level "ERROR"
                }
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Stop-ProcessesUsingOneDriveLib function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Stop-ProcessesUsingOneDriveLib function" -Level "INFO"
    }
}

# Example usage
# Stop-ProcessesUsingOneDriveLib -OneDriveLibPath "C:\ProgramData\AADMigration\Files\OneDriveLib.dll"
#EndRegion '.\Public\Stop-ProcessesUsingOneDriveLib.ps1' 52
#Region '.\Public\Suspend-BitLockerWithReboot.ps1' -1

function Suspend-BitLockerWithReboot {
    <#
    .SYNOPSIS
    Suspends BitLocker and configures the system to reboot a specified number of times.
 
    .DESCRIPTION
    The Suspend-BitLockerWithReboot function suspends BitLocker protection on the specified drive and configures the system to reboot a specified number of times.
 
    .PARAMETER MountPoint
    The drive letter of the BitLocker protected drive.
 
    .PARAMETER RebootCount
    The number of reboots to suspend BitLocker protection for.
 
    .EXAMPLE
    $params = @{
        MountPoint = "C:"
        RebootCount = 2
    }
    Suspend-BitLockerWithReboot @params
    Suspends BitLocker on drive C: for 2 reboots.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$MountPoint,

        [Parameter(Mandatory = $true)]
        [int]$RebootCount
    )

    Begin {
        Write-EnhancedLog -Message "Starting Suspend-BitLockerWithReboot function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            Write-EnhancedLog -Message "Suspending BitLocker on drive $MountPoint for $RebootCount reboots" -Level "INFO"
            Suspend-BitLocker -MountPoint $MountPoint -RebootCount $RebootCount -Verbose
            Write-EnhancedLog -Message "Successfully suspended BitLocker on drive $MountPoint" -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while suspending BitLocker: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Suspend-BitLockerWithReboot function" -Level "Notice"
    }
}

# # Example usage
# $params = @{
# MountPoint = "C:"
# RebootCount = 2
# }
# Suspend-BitLockerWithReboot @params
#EndRegion '.\Public\Suspend-BitLockerWithReboot.ps1' 62
#Region '.\Public\Test-Bitlocker.ps1' -1

function Test-Bitlocker {
    <#
    .SYNOPSIS
    Tests if BitLocker is enabled on the specified drive.
 
    .DESCRIPTION
    The Test-Bitlocker function tests if BitLocker is enabled on the specified drive.
 
    .PARAMETER BitlockerDrive
    The drive letter of the BitLocker protected drive.
 
    .EXAMPLE
    Test-Bitlocker -BitlockerDrive "C:"
    Tests if BitLocker is enabled on drive C:.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$BitlockerDrive
    )

    Begin {
        Write-EnhancedLog -Message "Starting Test-Bitlocker function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            $bitlockerVolume = Get-BitLockerVolume -MountPoint $BitlockerDrive -ErrorAction Stop
            Write-EnhancedLog -Message "BitLocker is enabled on drive: $BitlockerDrive" -Level "INFO"
            return $bitlockerVolume
        }
        catch {
            Write-EnhancedLog -Message "BitLocker is not enabled on drive: $BitlockerDrive. Terminating script!" -Level "ERROR"
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Test-Bitlocker function" -Level "Notice"
    }
}
#EndRegion '.\Public\Test-Bitlocker.ps1' 44
#Region '.\Public\Trigger-ScheduledTask.ps1' -1

function Trigger-ScheduledTask {
    [CmdletBinding()]
    param (
        [string]$TaskPath,
        [string]$TaskName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Trigger-ScheduledTask function" -Level "NOTICE"
        CheckAndElevate -ElevateIfNotAdmin $true
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Validate if the scheduled task exists before triggering
            $isTaskValid = Validate-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName
            if (-not $isTaskValid) {
                Write-EnhancedLog -Message "Validation failed. The scheduled task '$TaskName' does not exist or is invalid." -Level "ERROR"
                return
            }

            # Proceed with triggering the task if validation passed
            Write-EnhancedLog -Message "Triggering the scheduled task '$TaskName' under the '$TaskPath' folder..." -Level "INFO"

            $startTaskParams = @{
                TaskPath = $TaskPath
                TaskName = $TaskName
            }

            Start-ScheduledTask @startTaskParams

            Write-EnhancedLog -Message "Scheduled task triggered successfully." -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while triggering the scheduled task: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Trigger-ScheduledTask function" -Level "NOTICE"
    }
}
#EndRegion '.\Public\Trigger-ScheduledTask.ps1' 46
#Region '.\Public\Upload-LogsToGitHub.ps1' -1

function Upload-LogsToGitHub {
    <#
    .SYNOPSIS
    Upload zipped log files to a GitHub repository.
 
    .DESCRIPTION
    This function compresses log files from a specified directory, clones a GitHub repository,
    and uploads the zipped logs to the repository using Git. It handles Git user configuration
    based on whether the script is running as SYSTEM or a regular user.
 
    .PARAMETER SecurePAT
    A secure string containing the GitHub Personal Access Token (PAT) for authentication.
 
    .PARAMETER GitExePath
    The path to the Git executable. Defaults to "C:\Program Files\Git\bin\git.exe".
 
    .PARAMETER LogsFolderPath
    The path to the folder containing logs to be uploaded.
 
    .PARAMETER TempCopyPath
    A temporary directory where the logs will be copied before zipping.
 
    .PARAMETER TempGitPath
    A temporary directory for Git operations.
 
    .PARAMETER GitUsername
    Your GitHub username.
 
    .PARAMETER BranchName
    The Git branch to push the commits to. Defaults to "main".
 
    .PARAMETER CommitMessage
    The message to be used for the Git commit.
 
    .PARAMETER RepoName
    The name of the GitHub repository to which the logs will be pushed.
 
    .PARAMETER JobName
    The name of the job, which will be used for folder organization inside the Git repository.
 
    .EXAMPLE
    $params = @{
        SecurePAT = $securePat
        GitExePath = "C:\Program Files\Git\bin\git.exe"
        LogsFolderPath = "C:\logs"
        TempCopyPath = "C:\temp-logs"
        TempGitPath = "C:\temp-git"
        GitUsername = "aollivierre"
        BranchName = "main"
        CommitMessage = "Add logs.zip"
        RepoName = "syslog"
        JobName = "AADMigration"
    }
 
    Upload-LogsToGitHub @params
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "GitHub Personal Access Token as a SecureString")]
        [SecureString]$SecurePAT,

        [Parameter(Mandatory = $false, HelpMessage = "Path to Git executable", Position = 1)]
        [string]$GitExePath = "C:\Program Files\Git\bin\git.exe",

        [Parameter(Mandatory = $false, HelpMessage = "Path to logs folder", Position = 2)]
        [string]$LogsFolderPath = "C:\logs",

        [Parameter(Mandatory = $false, HelpMessage = "Temporary directory for logs copy", Position = 3)]
        [string]$TempCopyPath = "C:\temp-logs",

        [Parameter(Mandatory = $false, HelpMessage = "Temporary directory for Git operations", Position = 4)]
        [string]$TempGitPath = "C:\temp-git",

        [Parameter(Mandatory = $true, HelpMessage = "GitHub username", Position = 5)]
        [string]$GitUsername,

        [Parameter(Mandatory = $false, HelpMessage = "Branch to push changes to", Position = 6)]
        [string]$BranchName = "main",

        [Parameter(Mandatory = $false, HelpMessage = "Git commit message", Position = 7)]
        [string]$CommitMessage = "Add logs.zip",

        [Parameter(Mandatory = $true, HelpMessage = "Name of the GitHub repository", Position = 8)]
        [string]$RepoName,

        [Parameter(Mandatory = $false, HelpMessage = "Job name for folder structure", Position = 9)]
        [string]$JobName = "AADMigration"
    )

    try {
        Write-EnhancedLog -Message "Starting Upload-LogsToGitHub function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Convert SecureString to plain text
        $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePAT)
        $PersonalAccessToken = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr)

        # Build Git repo URL
        $RepoUrlSanitized = "https://github.com/$GitUsername/$RepoName.git"
        $RepoUrl = "https://{0}:{1}@github.com/{2}/$RepoName.git" -f $GitUsername, $PersonalAccessToken, $GitUsername

        # Clean up temp Git path if it exists
        if (Test-Path -Path $TempGitPath) {
            Write-EnhancedLog -Message "Removing $TempGitPath..."
            Remove-Item -Path $TempGitPath -Recurse -Force
        }

        # Ensure temp directories exist
        if (-not (Test-Path -Path $TempGitPath)) {
            New-Item -Path $TempGitPath -ItemType Directory | Out-Null
        }

        if (Test-Path -Path $TempCopyPath) {
            Write-EnhancedLog -Message "Removing $TempCopyPath..."
            Remove-Item -Path $TempCopyPath -Recurse -Force
        }

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

        # Copy logs to temp path
        Copy-FilesWithRobocopy -Source $LogsFolderPath -Destination $TempCopyPath -FilePattern '*' -Exclude ".git"

        # Zip the copied logs
        $TempZipFile = Join-Path -Path $TempGitPath -ChildPath "logs.zip"
        $params = @{
            SourceDirectory = $TempCopyPath
            ZipFilePath     = $TempZipFile
        }
        Zip-Directory @params

        # Ensure zip file was created
        if (-Not (Test-Path -Path $TempZipFile)) {
            Write-EnhancedLog -Message "Failed to zip the logs folder." -ForegroundColor Red
            exit 1
        }

        # Clone the repository
        Set-Location -Path $TempGitPath
        $RepoPath = Join-Path -Path $TempGitPath -ChildPath $RepoName
        if (-Not (Test-Path -Path $RepoPath)) {
            Write-EnhancedLog -Message "Cloning repository from $RepoUrlSanitized..."
            & "$GitExePath" clone $RepoUrl
        }

        # Set up folder structure for logs
        $ComputerName = $env:COMPUTERNAME
        $CurrentDate = Get-Date -Format "yyyy-MM-dd"
        $CurrentTime = Get-Date -Format "h-mm-tt"  # Example: 7-08-AM
        $JobFolder = Join-Path -Path $RepoPath -ChildPath "$ComputerName\$CurrentDate\$CurrentTime\$JobName"

        # Ensure the directory structure exists
        if (-Not (Test-Path -Path $JobFolder)) {
            New-Item -Path $JobFolder -ItemType Directory -Force | Out-Null
        }

        # Copy the zip file to the repository folder
        Copy-Item -Path $TempZipFile -Destination $JobFolder -Force

        # Configure Git user identity based on account type
        Set-Location -Path $RepoPath
        $IsSystem = Test-RunningAsSystem
        if ($IsSystem) {
            & "$GitExePath" config user.email "system@example.com"
            & "$GitExePath" config user.name "System User"
            Write-EnhancedLog -Message "Configured Git identity for SYSTEM account." -Level "INFO"
        }
        else {
            $CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
            $CurrentUserEmail = "$($CurrentUser.Replace('\', '_'))@example.com"
            & "$GitExePath" config user.email $CurrentUserEmail
            & "$GitExePath" config user.name $CurrentUser
            Write-EnhancedLog -Message "Configured Git identity for user: $CurrentUser." -Level "INFO"
        }

        # Add, commit, and push changes to the repository
        & "$GitExePath" add *
        & "$GitExePath" commit -m "$CommitMessage from $ComputerName on $CurrentDate"
        & "$GitExePath" push origin $BranchName

        Write-EnhancedLog -Message "Zipped log file copied to $JobFolder and pushed to the repository." -ForegroundColor Green

        # Clean up temporary directories
        Set-Location -Path "C:\" # Ensure we're not inside the Git directory
        Remove-Item -Path $TempGitPath -Recurse -Force
        Write-EnhancedLog -Message "Process completed and temp $TempGitPath directory cleaned up." -ForegroundColor Green

    }
    catch {
        Handle-Error -ErrorRecord $_
    }
    finally {
        Write-EnhancedLog -Message "Exiting Upload-LogsToGitHub function" -Level "NOTICE"
    }
}
#EndRegion '.\Public\Upload-LogsToGitHub.ps1' 198
#Region '.\Public\Validate-OneDriveLibUsage.ps1' -1

function Validate-OneDriveLibUsage {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$OneDriveLibPath
    )

    $processesUsingLib = [System.Collections.Generic.List[PSCustomObject]]::new()

    try {
        # Get all processes
        $processes = Get-Process

        # Iterate over each process and check if it has loaded OneDriveLib.dll
        foreach ($process in $processes) {
            try {
                $modules = $process.Modules | Where-Object { $_.FileName -eq $OneDriveLibPath }
                if ($modules) {
                    $processesUsingLib.Add([PSCustomObject]@{
                        ProcessName = $process.ProcessName
                        ProcessId   = $process.Id
                    })
                }
            }
            catch {
                # Handle any errors encountered while accessing process modules
                Write-EnhancedLog -Message "Could not access modules for process: $($process.ProcessName) (ID: $($process.Id)). Error: $($_.Exception.Message)" -Level "WARNING"
            }
        }
    }
    catch {
        Write-EnhancedLog -Message "An error occurred in Validate-OneDriveLibUsage function: $($_.Exception.Message)" -Level "ERROR"
        Handle-Error -ErrorRecord $_
    }

    return $processesUsingLib
}
#EndRegion '.\Public\Validate-OneDriveLibUsage.ps1' 38
#Region '.\Public\Validate-PPKGInstallation.ps1' -1


function Validate-PPKGInstallation {
    <#
    .SYNOPSIS
    Validates whether a provisioning package (PPKG) is installed on the system.
 
    .DESCRIPTION
    The Validate-PPKGInstallation function checks if a specified provisioning package (PPKG) is installed on the system by comparing its name with installed packages and validating the `IsInstalled` property. If found and installed, the function returns `$true`, otherwise `$false`.
 
    .PARAMETER PPKGName
    The name (or partial name) of the provisioning package (PPKG) to validate.
 
    .EXAMPLE
    # Example: Validate if the "ICTC_Project_2" PPKG is installed
    $ppkgInstalled = Validate-PPKGInstallation -PPKGName "ICTC_Project_2"
    if ($ppkgInstalled) {
        Write-Host "Provisioning package is installed."
    } else {
        Write-Host "Provisioning package is not installed."
    }
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Specify the name of the provisioning package (PPKG) to validate.")]
        [ValidateNotNullOrEmpty()]
        [string]$PPKGName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Validate-PPKGInstallation function for $PPKGName" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Fetch all installed provisioning packages
            Write-EnhancedLog -Message "Fetching all installed provisioning packages..." -Level "INFO"
            $installedPackages = Get-ProvisioningPackage -AllInstalledPackages
    
            # Log the number of installed packages found
            $packageCount = $installedPackages.Count
            Write-EnhancedLog -Message "Found $packageCount installed provisioning packages." -Level "INFO"

            # Check if no packages are found and exit the script
            if ($packageCount -eq 0) {
                Write-EnhancedLog -Message "No provisioning packages found. Exiting script." -Level "WARNING"
                return
            }

    
            # Filter the packages based on the provided PPKG name
            Write-EnhancedLog -Message "Searching for a package matching: *$PPKGName*" -Level "INFO"
    
            $ppkgInfo = $null
            foreach ($package in $installedPackages) {
                Write-EnhancedLog -Message "Checking package: `nPackage ID: $($package.PackageId)`nPackage Name: $($package.PackageName)`nPackage Path: $($package.PackagePath)" -Level "INFO"
    
                # Match on PackageName, but also validate IsInstalled
                if ($package.PackageName -like "*$PPKGName*" -and $package.IsInstalled) {
                    Write-EnhancedLog -Message "Match found and installed: `nPackage ID: $($package.PackageId)`nPackage Name: $($package.PackageName)`nPackage Path: $($package.PackagePath)" -Level "INFO"
                    $ppkgInfo = $package
                    break
                }
                else {
                    Write-EnhancedLog -Message "No match or package not installed: $($package.PackageName)" -Level "INFO"
                }
            }
    
            # Log if a matching package was found or not
            if ($ppkgInfo) {
                Write-EnhancedLog -Message "Provisioning package $($ppkgInfo.PackageName) is installed." -Level "INFO"
                return $true
            }
            else {
                Write-EnhancedLog -Message "No matching installed package found for: *$PPKGName*" -Level "ERROR"
                return $false
            }
        }
        catch {
            # Log and handle any errors encountered during validation
            Write-EnhancedLog -Message "An error occurred during PPKG validation: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Validate-PPKGInstallation function for $PPKGName" -Level "NOTICE"
    }
}

# # Example: Validate if a specific provisioning package is installed
# $ppkgInstalled = Validate-PPKGInstallation -PPKGName "MyProvisioningPackage"
# if ($ppkgInstalled) {
# Write-Host "Provisioning package is installed."
# }
# else {
# Write-Host "Provisioning package is not installed."
# }
#EndRegion '.\Public\Validate-PPKGInstallation.ps1' 101
#Region '.\Public\Validate-RegistryValue.ps1' -1

function Validate-RegistryValue {
    <#
    .SYNOPSIS
    Validates that a registry value is set correctly.
 
    .DESCRIPTION
    The Validate-RegistryValue function checks whether a registry value matches the expected data.
 
    .PARAMETER RegKeyPath
    The path to the registry key.
 
    .PARAMETER RegValName
    The name of the registry value.
 
    .PARAMETER ExpectedValData
    The expected data of the registry value.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$RegKeyPath,

        [Parameter(Mandatory = $true)]
        [string]$RegValName,

        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$ExpectedValData
    )

    Begin {
        Write-EnhancedLog -Message "Starting Validate-RegistryValue function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            $CurrentValue = Get-ItemPropertyValue -Path $RegKeyPath -Name $RegValName
            if ($CurrentValue -eq $ExpectedValData) {
                Write-EnhancedLog -Message "Registry value: $RegValName is set correctly with data: $ExpectedValData" -Level "INFO"
                return $true
            }
            else {
                Write-EnhancedLog -Message "Registry value: $RegValName is not set correctly. Expected: $ExpectedValData, Found: $CurrentValue" -Level "WARNING"
                return $false
            }
        }
        catch {
            Write-EnhancedLog -Message "Registry value: $RegValName not found at $RegKeyPath" -Level "WARNING"
            return $false
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Validate-RegistryValue function" -Level "Notice"
    }
}

# Example usage:
# $params = @{
# RegKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
# RegValName = "AutoAdminLogon"
# RegValType = "DWORD"
# RegValData = "0"
# }
# Set-RegistryValue @params
#EndRegion '.\Public\Validate-RegistryValue.ps1' 68
#Region '.\Public\Validate-ScheduledTask.ps1' -1

function Validate-ScheduledTask {
    <#
    .SYNOPSIS
    Validates whether a scheduled task exists and meets the expected criteria.
 
    .DESCRIPTION
    The Validate-ScheduledTask function checks if a scheduled task exists at the specified task path and validates its properties.
 
    .PARAMETER TaskPath
    The path of the task in Task Scheduler.
 
    .PARAMETER TaskName
    The name of the scheduled task.
 
    .EXAMPLE
    Validate-ScheduledTask -TaskPath "AAD Migration" -TaskName "Run Post-migration cleanup"
    Validates if the scheduled task exists and meets the expected criteria.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TaskPath,

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

    Begin {
        Write-EnhancedLog -Message "Starting Validate-ScheduledTask function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            $taskExists = Get-ScheduledTask -TaskPath "\$TaskPath\" -TaskName $TaskName -ErrorAction SilentlyContinue
            if ($taskExists) {
                Write-EnhancedLog -Message "Scheduled task '$TaskName' exists at '$TaskPath'." -Level "INFO"
                return $true
            } else {
                Write-EnhancedLog -Message "Scheduled task '$TaskName' does not exist at '$TaskPath'." -Level "WARNING"
                return $false
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Validate-ScheduledTask function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Validate-ScheduledTask function" -Level "Notice"
    }
}
#EndRegion '.\Public\Validate-ScheduledTask.ps1' 56
#Region '.\Public\Verify-GroupMembers-Archive.ps1' -1

# function Verify-GroupMembers {
# param (
# [string]$GroupName = 'Administrators'
# )

# $groupMembers = Get-GroupMembers -GroupName $GroupName
# foreach ($member in $groupMembers) {
# Write-EnhancedLog -Message "Processing member: $($member.Name)" -Level "INFO"

# $sid = Resolve-SID -AccountName $member.Name
# if ($sid) {
# Write-EnhancedLog -Message "Resolved SID for member $($member.Name): $($sid.Value)" -Level "INFO"
# } else {
# Write-EnhancedLog -Message "Skipping member $($member.Name) due to invalid or unresolved SID." -Level "WARNING"
# }
# }
# }
#EndRegion '.\Public\Verify-GroupMembers-Archive.ps1' 18