WinProfileOps.psm1

#Region './prefix.ps1' -1

# Your functions

# Check if the current user is an administrator
# Check if the current user is an administrator
$windowsIdentity = [Security.Principal.WindowsIdentity]::GetCurrent()
$windowsPrincipal = New-Object Security.Principal.WindowsPrincipal($windowsIdentity)
$env:WinProfileOps_IsAdmin = $windowsPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

# Prepopulate other environment variables if they don't already exist

if (-not $env:WinProfileOps_RegistryPath)
{
    $env:WinProfileOps_RegistryPath = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
}
if (-not $env:WinProfileOps_RegistryHive)
{
    $env:WinProfileOps_RegistryHive = [Microsoft.Win32.RegistryHive]::LocalMachine
}
if (-not $env:WinProfileOps_RegBackUpDirectory)
{
    $env:WinProfileOps_RegBackUpDirectory = "C:\LHStuff\RegBackUp"
}
if (-not $env:WinProfileOps_ProfileFolderPath)
{
    $env:WinProfileOps_ProfileFolderPath = $env:SystemDrive + "\Users"
}

# Prepopulate IgnoredAccounts, IgnoredSIDs, and IgnoredPaths if they aren't set
if (-not $env:WinProfileOps_IgnoredAccounts)
{
    $env:WinProfileOps_IgnoredAccounts = "defaultuser0;DefaultAppPool;servcm12;Public;PBIEgwService;Default;All Users;win2kpro"
}

if (-not $env:WinProfileOps_IgnoredSIDs)
{
    $env:WinProfileOps_IgnoredSIDs = "S-1-5-18;S-1-5-19;S-1-5-20"

}

if (-not $env:WinProfileOps_IgnoredPaths)
{
    $env:WinProfileOps_IgnoredPaths = "C:\WINDOWS\system32\config\systemprofile;C:\WINDOWS\ServiceProfiles\LocalService;C:\WINDOWS\ServiceProfiles\NetworkService"
}


[scriptblock]$SB = {
    Get-ChildItem "Env:\WinProfileOps*" | ForEach-Object {
        Remove-Item "Env:\$($_.Name)" -ErrorAction SilentlyContinue
    }
}

Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
    $sb.Invoke()
}

# Define the OnRemove script block for the module
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
    $sb.Invoke()
}
#EndRegion './prefix.ps1' 60
#Region './Classes/ProfileDeletionResult.ps1' -1

class ProfileDeletionResult
{
    [string]$SID
    [string]$ProfilePath
    [bool]$DeletionSuccess
    [string]$DeletionMessage
    [string]$ComputerName

    # Constructor 1: Full constructor
    ProfileDeletionResult([string]$sid, [string]$profilePath, [bool]$deletionSuccess, [string]$deletionMessage, [string]$computerName)
    {
        $this.SID = $sid
        $this.ProfilePath = $profilePath
        $this.DeletionSuccess = $deletionSuccess
        $this.DeletionMessage = $deletionMessage
        $this.ComputerName = $computerName
    }

    # Constructor 2: Only SID and DeletionSuccess, with default values for others
    ProfileDeletionResult([string]$sid, [bool]$deletionSuccess)
    {
        $this.SID = $sid
        $this.ProfilePath = $null
        $this.DeletionSuccess = $deletionSuccess
        if ($deletionSuccess)
        {
            $this.DeletionMessage = "Operation successful"
        }
        else
        {
            $this.DeletionMessage = "Operation failed"
        }
        $this.ComputerName = $env:COMPUTERNAME
    }

    # Constructor 3: Minimal constructor with defaults for all except SID
    ProfileDeletionResult([string]$sid)
    {
        $this.SID = $sid
        $this.ProfilePath = $null
        $this.DeletionSuccess = $false
        $this.DeletionMessage = "No action performed"
        $this.ComputerName = $env:COMPUTERNAME
    }

    # Optional method
    [string] ToString()
    {
        return "[$($this.SID)] DeletionSuccess: $($this.DeletionSuccess), Message: $($this.DeletionMessage)"
    }
}
#EndRegion './Classes/ProfileDeletionResult.ps1' 52
#Region './Classes/RegUserProfile.ps1' -1

class RegUserProfile {
    [string]$SID
    [string]$ProfilePath
    [bool]$IsOrphaned
    [string]$OrphanReason
    [string]$ComputerName
    [bool]$IsSpecial
    [bool]$IsLoaded
    [string]$UserName
    [string]$Domain
    [datetime]$LastLogonDate
    [long]$ProfileSize
    [string]$ProfileType
    [datetime]$CreatedDate
    [string]$ProfileStatus
    [bool]$IsTemporary
    [bool]$IsCorrupted
    [string[]]$SecurityGroups
    [string]$HomeDirectory
    [bool]$IsEncrypted
    [string]$ProfileState
    [string]$LastUsedApp
    [bool]$HasBackup
    [bool]$IsRoaming
    [datetime]$LastModifiedDate
    [bool]$IsAdminProfile

    # New properties
    [bool]$HasUserFolder  # Indicates if the user's folder exists
    [datetime]$Created  # The DateTime when the object was instantiated

    # Constructor
    RegUserProfile(
        [string]$SID,
        [string]$ProfilePath,
        [bool]$IsOrphaned,
        [string]$OrphanReason = $null,
        [string]$ComputerName,
        [bool]$IsSpecial,
        [bool]$IsLoaded,
        [string]$UserName,
        [string]$Domain,
        [datetime]$LastLogonDate,
        [long]$ProfileSize,
        [string]$ProfileType,
        [datetime]$CreatedDate,
        [string]$ProfileStatus,
        [bool]$IsTemporary,
        [bool]$IsCorrupted,
        [string[]]$SecurityGroups,
        [string]$HomeDirectory,
        [bool]$IsEncrypted,
        [string]$ProfileState,
        [string]$LastUsedApp,
        [bool]$HasBackup,
        [bool]$IsRoaming,
        [datetime]$LastModifiedDate,
        [bool]$IsAdminProfile,
        [bool]$HasUserFolder  # New property
    ) {
        # Initialize all properties
        $this.SID = $SID
        $this.ProfilePath = $ProfilePath
        $this.IsOrphaned = $IsOrphaned
        $this.OrphanReason = $OrphanReason
        $this.ComputerName = $ComputerName
        $this.IsSpecial = $IsSpecial
        $this.IsLoaded = $IsLoaded
        $this.UserName = $UserName
        $this.Domain = $Domain
        $this.LastLogonDate = $LastLogonDate
        $this.ProfileSize = $ProfileSize
        $this.ProfileType = $ProfileType
        $this.CreatedDate = $CreatedDate
        $this.ProfileStatus = $ProfileStatus
        $this.IsTemporary = $IsTemporary
        $this.IsCorrupted = $IsCorrupted
        $this.SecurityGroups = $SecurityGroups
        $this.HomeDirectory = $HomeDirectory
        $this.IsEncrypted = $IsEncrypted
        $this.ProfileState = $ProfileState
        $this.LastUsedApp = $LastUsedApp
        $this.HasBackup = $HasBackup
        $this.IsRoaming = $IsRoaming
        $this.LastModifiedDate = $LastModifiedDate
        $this.IsAdminProfile = $IsAdminProfile
        $this.HasUserFolder = $HasUserFolder
        $this.Created = [DateTime]::Now  # Automatically set when object is created
    }

    # JSON Serialization Example
    [string] ToJson() {
        $properties = @{
            SID               = $this.SID
            ProfilePath       = $this.ProfilePath
            IsOrphaned        = $this.IsOrphaned
            OrphanReason      = $this.OrphanReason
            ComputerName      = $this.ComputerName
            IsSpecial         = $this.IsSpecial
            IsLoaded          = $this.IsLoaded
            UserName          = $this.UserName
            Domain            = $this.Domain
            LastLogonDate     = $this.LastLogonDate
            ProfileSize       = $this.ProfileSize
            ProfileType       = $this.ProfileType
            CreatedDate       = $this.CreatedDate
            ProfileStatus     = $this.ProfileStatus
            IsTemporary       = $this.IsTemporary
            IsCorrupted       = $this.IsCorrupted
            SecurityGroups    = $this.SecurityGroups
            HomeDirectory     = $this.HomeDirectory
            IsEncrypted       = $this.IsEncrypted
            ProfileState      = $this.ProfileState
            LastUsedApp       = $this.LastUsedApp
            HasBackup         = $this.HasBackup
            IsRoaming         = $this.IsRoaming
            LastModifiedDate  = $this.LastModifiedDate
            IsAdminProfile    = $this.IsAdminProfile
            HasUserFolder     = $this.HasUserFolder
            Created           = $this.Created  # Include the new Created DateTime property
        }
        return $properties | ConvertTo-Json
    }
}
#EndRegion './Classes/RegUserProfile.ps1' 125
#Region './Classes/UserProfile.ps1' -1

class UserProfile
{
    [datetime]$Created
    [string]$UserName
    [string]$SID
    [string]$ProfilePath
    [string]$FolderPath
    [string]$ProfileState
    [bool]$HasRegistryEntry
    [bool]$HasUserFolder
    [datetime]$LastLogonDate
    [datetime]$LastLogOffDate
    [bool]$IsOrphaned
    [string]$OrphanReason
    [string]$ComputerName
    [bool]$IsSpecial
    [bool]$IsLoaded
    [string]$Domain

    UserProfile(
        [string]$SID,
        [string]$UserName,
        [string]$ProfilePath,
        [string]$FolderPath,
        [string]$ProfileState,
        [bool]$HasRegistryEntry,
        [bool]$HasUserFolder,
        [datetime]$LastLogonDate,
        [datetime]$LastLogOffDate,
        [bool]$IsOrphaned,
        [string]$OrphanReason,
        [string]$ComputerName,
        [bool]$IsSpecial,
        [bool]$IsLoaded,
        [string]$Domain
    )
    {
        $this.Created = [DateTime]::Now
        $this.SID = $SID
        $this.UserName = $UserName
        $this.ProfilePath = $ProfilePath
        $this.FolderPath = $FolderPath
        $this.ProfileState = $ProfileState
        $this.HasRegistryEntry = $HasRegistryEntry
        $this.HasUserFolder = $HasUserFolder
        $this.LastLogonDate = if ($LastLogonDate -eq $null) { [datetime]::MinValue } else { $LastLogonDate }
        $this.LastLogOffDate = if ($LastLogOffDate -eq $null) { [datetime]::MinValue } else { $LastLogOffDate }
        $this.IsOrphaned = $IsOrphaned
        $this.OrphanReason = $OrphanReason
        $this.ComputerName = $ComputerName
        $this.IsSpecial = $IsSpecial
        $this.IsLoaded = $IsLoaded
        $this.Domain = $Domain
    }
}
#EndRegion './Classes/UserProfile.ps1' 56
#Region './Private/Get-ProfileRegistryItems/Get-ProfileRegistryItems.ps1' -1

<#
.SYNOPSIS
Retrieves profile registry items from a specified registry path on a remote computer.

.DESCRIPTION
The Get-ProfileRegistryItems function connects to a remote computer and retrieves profile registry items from a specified registry path. It supports specifying the registry hive and handles opening and closing registry keys.

.PARAMETER RegistryPath
The registry path to retrieve profile items from. This parameter is mandatory and must not be null or empty.

.PARAMETER ComputerName
The name of the remote computer to connect to. This parameter is mandatory and must not be null or empty.

.PARAMETER RegistryHive
The registry hive to use. Defaults to 'LocalMachine'. This parameter is optional.

.EXAMPLE
Get-ProfileRegistryItems -RegistryPath 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -ComputerName 'RemotePC'

This example retrieves profile registry items from the specified registry path on the remote computer 'RemotePC'.

.EXAMPLE
'SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' | Get-ProfileRegistryItems -ComputerName 'RemotePC'

This example retrieves profile registry items from the specified registry path on the remote computer 'RemotePC', using pipeline input for the registry path.
.NOTES
#>

function Get-ProfileRegistryItems
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$RegistryPath,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ComputerName,

        [Parameter()]
        [string]$RegistryHive = 'LocalMachine'
    )

    begin
    {

        # Explicitly check if the RegistryPath is of type string
        if (-not $RegistryPath -is [string])
        {
            throw "The parameter 'RegistryPath' must be a string."
        }

        $ProfileRegistryItems = @()  # Initialize an empty array to store profile items

        Write-Verbose "Opening HKEY_USERS hive on $ComputerName"
        $HKEYUsers = Open-RegistryKey -RegistryHive Users -ComputerName $ComputerName -Writable $false
        if (-not $HKEYUsers)
        {
            throw "Failed to open HKEY_USERS on $ComputerName."
        }

        # Get subkeys of HKEY_USERS once
        $HKEYUsersSubkeyNames = $HKEYUsers.GetSubKeyNames()
    }


    process
    {
        Write-Verbose "Processing registry path: $RegistryPath on $ComputerName"

        # Open the registry path on the target computer
        $ProfileListKey = Open-RegistryKey -RegistryPath $RegistryPath -ComputerName $ComputerName -Writable $false -RegistryHive $RegistryHive
        if (-not $ProfileListKey)
        {
            Write-Error "Failed to open registry path: $RegistryPath on $ComputerName."
            return
        }

        # Retrieve the list of SIDs from the registry key
        $subKeyNames = $ProfileListKey.GetSubKeyNames()
        if (-not $subKeyNames -or $subKeyNames.Count -eq 0)
        {
            Write-Verbose "No SIDs found in the registry key on $ComputerName."
            return
        }

        $subKeyNames | Invoke-ProfileRegistryItemProcessing -ComputerName $ComputerName -ProfileListKey $ProfileListKey -HKEYUsersSubkeyNames $HKEYUsersSubkeyNames
    }

    end
    {
        Write-Verbose "Returning profile registry items for $ComputerName"
        # Close Keys
        $ProfileListKey.Close()
        $HKEYUsers.Close()
    }
}
#EndRegion './Private/Get-ProfileRegistryItems/Get-ProfileRegistryItems.ps1' 98
#Region './Private/Get-ProfileRegistryItems/Invoke-ProfileRegistryItemProcessing.ps1' -1

<#
.SYNOPSIS
Processes a user profile registry item based on the provided SID and registry information.

.DESCRIPTION
The `Invoke-ProfileRegistryItemProcessing` function processes a user profile from the Windows registry, gathering information such as logon and logoff times, profile state, profile path, and special account status. It also verifies if the profile is loaded and if the user's folder exists on the specified computer. The function outputs a custom profile object to the pipeline containing the gathered data.

.PARAMETER Sid
Specifies the Security Identifier (SID) of the profile to process. This parameter is mandatory and accepts input from the pipeline.

.PARAMETER ComputerName
The name of the computer where the profile resides. This parameter is mandatory.

.PARAMETER ProfileListKey
The registry key that holds the profile information. This is typically the "ProfileList" key in the registry.

.PARAMETER HKEYUsersSubkeyNames
An array of subkey names from HKEY_USERS, used to determine if the profile SID is currently loaded. This parameter is optional.

.EXAMPLE
$SIDs = 'S-1-5-21-1234567890-123456789-1234567890-1001'
$computerName = 'TestComputer'
$profileListKey = Open-RegistrySubKey -BaseKey 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -Writable $false
$subkeyNames = Get-SubkeyNamesFromHKEYUsers

$SIDs | Invoke-ProfileRegistryItemProcessing -ComputerName $computerName -ProfileListKey $profileListKey -HKEYUsersSubkeyNames $subkeyNames

Description:
This example processes a single user profile on the specified computer by providing the SID, computer name, profile list registry key, and subkey names from HKEY_USERS. The function outputs a profile object to the pipeline.

.EXAMPLE
Get-ADUser -Filter * | ForEach-Object {
    Invoke-ProfileRegistryItemProcessing -Sid $_.SID -ComputerName 'TestComputer' -ProfileListKey $ProfileListKey
}

Description:
This example retrieves all Active Directory users and processes their corresponding profile registry entries on a specific computer, outputting profile objects to the pipeline.

.INPUTS
[string] - The SID of the user profile (pipeline input is accepted).
[string] - The computer name where the profile resides.
[Microsoft.Win32.RegistryKey] - The profile list registry key.
[string[]] - An array of HKEY_USERS subkey names.

.OUTPUTS
[PSCustomObject] - The processed profile object containing information about the user profile, including logon/logoff times, profile path, and special account status.

.NOTES
- The function handles exceptions like unauthorized access when accessing registry keys.
- It checks for the existence of a user folder and whether the profile is loaded.
- This function is designed to work both locally and remotely by providing the correct computer name and registry key information.

#>

function Invoke-ProfileRegistryItemProcessing
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$Sid,

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

        [Parameter(Mandatory = $true)]
        $ProfileListKey,

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

    process
    {
        Write-Verbose "Processing SID: $Sid"

        if (-not (Validate-SIDFormat -SID $Sid))
        {
            Write-Warning "Invalid SID format: $Sid"
            return
        }

        $subKey = Open-RegistrySubKey -BaseKey $ProfileListKey -Name $Sid -Writable $false
        if (-not $subKey)
        {
            Write-Warning "Registry key for SID '$Sid' could not be opened."
            return
        }

        # Initialize the parameter hashtable
        $ParameterHash = @{
            SID          = $Sid
            ComputerName = $ComputerName
            ErrorAccess  = $false
        }

        try
        {
            # Test and gather information
            $LogonDates = Get-LogonLogoffDatesFromRegistry -SubKey $subKey
            $ParameterHash.LastLogOnDate = $LogonDates.logonDate
            $ParameterHash.LastLogOffDate = $LogonDates.logoffDate

            $ProfileState = Get-ProfileStateFromRegistrySubKey -SubKey $subKey
            $ParameterHash.ProfileState = $ProfileState.StateText

            $profilePathResults = Get-ProfilePathFromSID -SidKey $subKey
            $ProfilePath = $profilePathResults.ProfileImagePath
            $ParameterHash.ProfilePath = $profilePath

            # Use $HKEYUsersSubkeyNames to determine if the SID is loaded
            $isLoaded = $HKEYUsersSubkeyNames -contains $Sid
            $ParameterHash.IsLoaded = $isLoaded

            # Check for user folder existence
            Write-Verbose "Checking for user folder existence: $profilePath"
            $HasUserFolder = Test-FolderExists -ProfilePath $ParameterHash.ProfilePath -ComputerName $ComputerName -ErrorAction Stop
            $ParameterHash.HasUserFolder = $HasUserFolder
        }
        catch [UnauthorizedAccessException]
        {
            Write-Warning "Access denied when processing SID '$Sid'."
            $ParameterHash.ErrorAccess = $true
            $ParameterHash.HasUserFolder = $true
        }
        catch
        {
            Write-Warning "Failed to retrieve registry data for SID '$Sid'. Error: $_"
            return
        }

        # Special account test
        $TestSpecialParams = @{ SID = $Sid }
        if ($profilePath)
        {
            $TestSpecialParams.Add("FolderName", (Split-Path -Path $profilePath -Leaf))
            $TestSpecialParams.Add("ProfilePath", $profilePath)
        }

        $IsSpecialResults = Test-SpecialAccount @TestSpecialParams
        $ParameterHash.IsSpecial = $IsSpecialResults.IsSpecial

        # Translate SID to user account information
        $accountInfo = Get-UserAccountFromSID -SID $Sid -ComputerName $ComputerName -WarningAction SilentlyContinue
        $ParameterHash.Domain = $accountInfo.Domain
        $ParameterHash.UserName = $accountInfo.Username

        # Invoke New-ProfileRegistryItemObject using the hashtable and output to the pipeline
        $ProfileRegistryItem = New-ProfileRegistryItemObject @ParameterHash

        $subKey.Close()
        # Output the item directly to the pipeline
        $ProfileRegistryItem
    }

}
#EndRegion './Private/Get-ProfileRegistryItems/Invoke-ProfileRegistryItemProcessing.ps1' 155
#Region './Private/Get-ProfileRegistryItems/New-ProfileRegistryItemObject.ps1' -1

<#
.SYNOPSIS
    Creates a custom object representing a user profile from registry and file system data.

.DESCRIPTION
    The New-ProfileRegistryItemObject function generates a PSCustomObject that holds detailed information about a user profile.
    This object includes properties such as the profile's SID, path, profile state, computer name, and other relevant metadata.
    It is useful for representing user profile information from both the registry and file system during system audits or troubleshooting tasks.

.PARAMETER SID
    The security identifier (SID) of the user profile.

.PARAMETER ProfilePath
    The file system path to the user profile directory.

.PARAMETER ProfileState
    A string representing the state of the user profile (e.g., active, inactive).

.PARAMETER ComputerName
    The name of the computer where the profile is located.

.PARAMETER HasRegistryEntry
    Indicates whether the profile has a corresponding entry in the registry. Defaults to $true.

.PARAMETER IsLoaded
    Indicates whether the user profile is currently loaded on the system.

.PARAMETER HasUserFolder
    Indicates whether a folder exists for the user profile in the file system.

.PARAMETER UserName
    The name of the user associated with the profile.

.PARAMETER Domain
    The domain associated with the user profile, if applicable.

.PARAMETER IsSpecial
    Indicates whether the profile is a special or system profile (e.g., Default or System profiles).

.PARAMETER LastLogOnDate
    The date and time of the user's last logon.

.PARAMETER LastLogOffDate
    The date and time of the user's last logoff.

.PARAMETER ErrorAccess
    Indicates if there was an error accessing the profile.

.PARAMETER errorCapture
    Captures any errors or additional information about access issues, if applicable.

.OUTPUTS
    PSCustomObject
    A custom object representing the user profile, with properties including the SID, ProfilePath, ProfileState, and other relevant data.

.EXAMPLE
    $profileObject = New-ProfileRegistryItemObject -SID 'S-1-5-21-1234567890-1001' -ProfilePath 'C:\Users\User1' -ProfileState 'Active' `
                                                     -ComputerName 'Server01' -IsLoaded $true -HasUserFolder $true -UserName 'User1' `
                                                     -Domain 'Domain' -IsSpecial $false -LastLogOnDate (Get-Date) -LastLogOffDate (Get-Date)

    Creates a profile object with the specified parameters for the user 'User1' on 'Server01'.

.NOTES
    This function is designed to consolidate user profile data from both the registry and file system into a single object,
    which can be used for audits, reports, or profile management tasks.
#>


function New-ProfileRegistryItemObject
{
    param (
        [string]$SID,
        [string]$ProfilePath,
        [string]$ProfileState,
        [string]$ComputerName,
        [bool]$HasRegistryEntry = $true,
        [bool]$IsLoaded,
        [bool]$HasUserFolder,
        [string]$UserName,
        [string]$Domain,
        [bool]$IsSpecial,
        [DateTime]$LastLogOnDate,
        [DateTime]$LastLogOffDate,
        [bool]$ErrorAccess,
        $errorCapture
    )

    return [pscustomobject]@{
        SID              = $SID
        ProfilePath      = $ProfilePath
        ProfileState     = $ProfileState
        ComputerName     = $ComputerName
        HasRegistryEntry = $HasRegistryEntry
        IsLoaded         = $IsLoaded
        HasUserFolder    = $HasUserFolder
        UserName         = $UserName
        Domain           = $Domain
        IsSpecial        = $IsSpecial
        LastLogOnDate    = $LastLogOnDate
        LastLogOffDate   = $LastLogOffDate
        ErrorAccess      = $ErrorAccess
        ErrorCapture     = $errorCapture
    }
}
#EndRegion './Private/Get-ProfileRegistryItems/New-ProfileRegistryItemObject.ps1' 104
#Region './Private/Get-RegistryKeyForSID.ps1' -1

<#
.SYNOPSIS
    Retrieves the registry key associated with a specified SID from the ProfileList.
.DESCRIPTION
    The Get-RegistryKeyForSID function attempts to open and retrieve the registry subkey for a given Security Identifier (SID) from the ProfileList. If the SID does not exist or an error occurs while accessing the registry, the function returns `$null` and logs a warning or error message.
.PARAMETER SID
    The Security Identifier (SID) for which to retrieve the registry subkey.
.PARAMETER ProfileListKey
    The opened registry key representing the ProfileList, which contains the subkeys for user profiles.
.EXAMPLE
    Get-RegistryKeyForSID -SID "S-1-5-21-123456789-1001" -ProfileListKey $profileListKey
    Retrieves the registry subkey associated with the specified SID from the ProfileList.
.NOTES
    If the registry key for the SID cannot be found or accessed, the function returns `$null` and logs an appropriate warning or error message.
    The function relies on the Open-RegistrySubKey function to retrieve the subkey.
#>

function Get-RegistryKeyForSID
{
    param (
        [string]$SID,
        [Microsoft.Win32.RegistryKey]$ProfileListKey
    )

    try
    {
        # Use the general Open-RegistrySubKey function to get the subkey for the SID
        $sidKey = Open-RegistrySubKey -BaseKey $ProfileListKey -Name $SID -writable $false
        if ($sidKey -eq $null)
        {
            Write-Warning "The SID '$SID' does not exist in the ProfileList registry."
            return $null
        }
        return $sidKey
    }
    catch
    {
        Write-Error "Error accessing registry key for SID '$SID'. Error: $_"
        return $null
    }
}
#EndRegion './Private/Get-RegistryKeyForSID.ps1' 41
#Region './Private/Helpers/Get-SIDFromUsername.ps1' -1

<#
.SYNOPSIS
    Retrieves the Security Identifier (SID) for a given username either locally or remotely.

.DESCRIPTION
    The `Get-SIDFromUsername` function resolves the Security Identifier (SID) associated with a given username by using the .NET `System.Security.Principal.NTAccount` class.
    The function allows execution on a local or remote computer by leveraging PowerShell's `Invoke-Command`.
    If the user exists and the SID can be resolved, the SID is returned; otherwise, a warning is displayed, and the function returns `$null`.

.PARAMETER Username
    Specifies the username for which to retrieve the SID. This parameter is mandatory and must not be null or empty.

.PARAMETER ComputerName
    Specifies the computer from which to retrieve the SID. If this parameter is not provided, the function will default to the local computer.
    When provided, the function will attempt to retrieve the SID from the specified remote computer.

.OUTPUTS
    String - The Security Identifier (SID) associated with the provided username.
    If the SID cannot be resolved, the function returns `$null`.

.NOTES
    This function uses the .NET `System.Security.Principal.NTAccount` class to resolve the username into a SID. It can query either the local system or a remote system (via PowerShell remoting).
    If PowerShell remoting is disabled or the specified remote computer is unreachable, the function will issue a warning and return `$null`.

.EXAMPLE
    Get-SIDFromUsername -Username 'JohnDoe'

    Description:
    Retrieves the SID for the user 'JohnDoe' from the local computer. If the user exists and the SID is found, it will be returned.

.EXAMPLE
    Get-SIDFromUsername -Username 'JohnDoe' -ComputerName 'Server01'

    Description:
    Retrieves the SID for the user 'JohnDoe' from the remote computer 'Server01'. If the user exists and the SID is found, it will be returned.

.EXAMPLE
    Get-SIDFromUsername -Username 'Administrator'

    Description:
    Retrieves the SID for the 'Administrator' account from the local computer. This works for both local and domain accounts.

.EXAMPLE
    Get-SIDFromUsername -Username 'Administrator' -ComputerName 'Server01'

    Description:
    Retrieves the SID for the 'Administrator' account from the remote computer 'Server01'. If the user account exists and the SID can be resolved, it will be returned.

.EXAMPLE
    $sids = @('User1', 'User2') | ForEach-Object { Get-SIDFromUsername -Username $_ }

    Description:
    Retrieves the SIDs for multiple users by passing the usernames through the pipeline and invoking the function for each user.

.EXAMPLE
    Get-SIDFromUsername -Username 'NonExistentUser'

    Warning:
    Failed to retrieve SID for username: NonExistentUser

    Output:
    $null

    Description:
    Attempts to retrieve the SID for a user that does not exist. In this case, the function issues a warning and returns `$null`.

#>

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

        [Parameter(Mandatory = $false)]
        [string]$ComputerName = $env:COMPUTERNAME  # Default to the local computer
    )

    try
    {
        # Define the script block for translating Username to SID
        $scriptBlock = {
            param ($Username)
            try
            {
                $ntAccount = New-Object System.Security.Principal.NTAccount($Username)
                $SID = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier])

                if ($null -ne $SID -and $null -ne $SID.Value)
                {
                    return $SID.Value
                }
                else
                {
                    return $null
                }
            }
            catch
            {
                return $null
            }
        }

        # Use Invoke-Command to run the script block locally or remotely
        $result = Invoke-Command -ComputerName $ComputerName -ScriptBlock $scriptBlock -ArgumentList $Username

        return $result
    }
    catch
    {
        Write-Warning "Failed to retrieve SID for username: $Username"
        return $null
    }
}
#EndRegion './Private/Helpers/Get-SIDFromUsername.ps1' 115
#Region './Private/Helpers/New-DirectoryIfNeeded.ps1' -1

<#
.SYNOPSIS
Creates a directory if it does not already exist.

.DESCRIPTION
The `New-DirectoryIfNeeded` function checks if the specified directory exists. If it doesn't, the function will create the directory and return the created directory object. If the directory already exists, the function returns `$true`. In case of any errors during directory creation, the function returns `$false` and logs the error.

.PARAMETER Directory
Specifies the full path of the directory to check or create. This parameter is mandatory. If the directory path is `null`, empty, or contains only whitespace, the function throws an error.

.EXAMPLE
New-DirectoryIfNeeded -Directory 'C:\Temp\NewFolder'

Description:
This command checks if the directory 'C:\Temp\NewFolder' exists. If it doesn't, the directory will be created. If the directory already exists, the function will return `$true`.

.EXAMPLE
New-DirectoryIfNeeded -Directory 'D:\Logs'

Description:
This command checks if the directory 'D:\Logs' exists. If it does not, the function will create the directory. If the directory already exists, it returns `$true`.

.EXAMPLE
$directory = New-DirectoryIfNeeded -Directory 'C:\Data\Reports'

Description:
This command attempts to create the directory 'C:\Data\Reports' if it doesn't exist and assigns the result to `$directory`. If successful, `$directory` will contain the created directory object. If the directory already exists, `$true` will be assigned to `$directory`.

.NOTES
If the directory path is invalid or if an error occurs during the creation process, the function writes an error message and returns `$false`.
#>


function New-DirectoryIfNeeded
{
    param (
        [Parameter(Mandatory = $true)]
        [string]$Directory
    )

    try
    {
        # Check if the Directory parameter is null or an empty string
        if ([string]::IsNullOrWhiteSpace($Directory))
        {
            throw [System.ArgumentException]::new("The 'Directory' parameter cannot be null or empty.")
        }

        # If the directory does not exist, attempt to create it
        if (-not (Test-Path -Path $Directory))
        {
            $newDirectory = New-Item -Path $Directory -ItemType Directory -Force -ErrorAction Stop
            return $newDirectory
        }

        # If the directory exists, return $true
        return $true
    }
    catch
    {
        Write-Error "Failed to create directory: $Directory. Error: $_"
        return $false
    }
}
#EndRegion './Private/Helpers/New-DirectoryIfNeeded.ps1' 64
#Region './Private/Helpers/ShouldContinueWrapper.ps1' -1

<#
.SYNOPSIS
Handles user confirmation prompts using the `ShouldContinue` method.

.DESCRIPTION
The `ShouldContinueWrapper` function prompts the user to confirm whether they want to proceed with an operation. It uses the `ShouldContinue` method from the execution context to display a message to the user. The function logs whether the user chose to continue or not and returns the result.

.PARAMETER Context
Specifies the execution context, typically used to invoke the `ShouldContinue` method.

.PARAMETER QueryMessage
Specifies the message to display to the user asking if they are sure they want to proceed.

.PARAMETER CaptionMessage
Specifies the caption of the confirmation prompt, providing additional context about the operation.

.EXAMPLE
$context = Get-ExecutionContext
ShouldContinueWrapper -Context $context -QueryMessage "Are you sure you want to delete these items?" -CaptionMessage "Confirm Deletion"

Description:
Prompts the user with the message "Are you sure you want to delete these items?" and the caption "Confirm Deletion". The function returns `$true` if the user chooses to continue, otherwise it returns `$false`.

.NOTES
This function assumes that it is called within an appropriate execution context where `ShouldContinue` can be invoked.
#>

function ShouldContinueWrapper
{
    param (
        [Parameter(Mandatory = $true)]
        $Context,

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

        [Parameter(Mandatory = $true)]
        [string]$CaptionMessage
    )
    $result = $Context.ShouldContinue($QueryMessage, $CaptionMessage)

    Write-Verbose "User chose to continue: $result"

    return $result
}
#EndRegion './Private/Helpers/ShouldContinueWrapper.ps1' 45
#Region './Private/Helpers/ShouldProcessWrapper.ps1' -1

<#
.SYNOPSIS
Handles the user confirmation for actions using the `ShouldProcess` method.

.DESCRIPTION
The `ShouldProcessWrapper` function prompts the user to confirm whether they want to proceed with a specified action on a specified target. It uses the `ShouldProcess` method from the execution context, logging the action and the target for verbose output. The function returns the result of the user's decision, allowing the calling function to proceed or halt based on the confirmation.

.PARAMETER Context
Specifies the execution context, typically used to invoke the `ShouldProcess` method.

.PARAMETER Target
Specifies the target of the action, such as a computer, file, or registry path, that the user is being asked to confirm.

.PARAMETER ActionMessage
Specifies the action that will be performed on the target, such as "Deleting", "Modifying", or "Stopping a service."

.EXAMPLE
$context = Get-ExecutionContext
ShouldProcessWrapper -Context $context -Target "Server01" -ActionMessage "Delete profiles"

Description:
Prompts the user to confirm if they want to proceed with deleting profiles from "Server01". The function logs the action and the target, then returns `$true` if the user agrees, otherwise returns `$false`.

.EXAMPLE
ShouldProcessWrapper -Context $context -Target "C:\Temp\File.txt" -ActionMessage "Remove the file"

Description:
Prompts the user with the message "Remove the file" for the target file "C:\Temp\File.txt". It logs the action and returns the user's response.

.NOTES
This function is typically used in cmdlets or scripts that support the `ShouldProcess` functionality to allow confirmation before destructive or critical actions.
#>


function ShouldProcessWrapper
{
    param (
        [Parameter(Mandatory = $true)]
        $Context,

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

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

    # Log the action message for verbose output
    Write-Verbose "About to perform action: $ActionMessage on $Target"

    # Use the ShouldProcess method from the context
    $result = $Context.ShouldProcess($Target, $ActionMessage)

    Write-Verbose "User chose to process: $result"

    return $result
}
#EndRegion './Private/Helpers/ShouldProcessWrapper.ps1' 57
#Region './Private/Helpers/Test-EnvironmentVariable.ps1' -1

<#
.SYNOPSIS
Validates the presence of a specific environment variable.

.DESCRIPTION
The Test-EnvironmentVariable function checks if the specified environment variable exists.
If the variable is found, it returns its value. If not, an error is thrown.

.PARAMETER Name
The name of the environment variable to check.

.EXAMPLE
Test-EnvironmentVariable -Name 'Path'

This command checks if the 'Path' environment variable is present and returns its value if found.

.OUTPUTS
String (Value of the environment variable)

.NOTES
This function will throw an error if the environment variable is missing from sesion.
#>

function Test-EnvironmentVariable
{
    param ([string]$Name)

    # Dynamically retrieve the environment variable
    $value = Get-Item -Path "Env:$Name" -ErrorAction SilentlyContinue

    if (-not $value)
    {
        throw "Missing required environment variable: $Name"
    }

    return $value.Value
}
#EndRegion './Private/Helpers/Test-EnvironmentVariable.ps1' 37
#Region './Private/Helpers/Update-JsonFile.ps1' -1

<#
.SYNOPSIS
Updates an existing JSON file with new registry data or creates a new file if one doesn't exist.

.DESCRIPTION
The `Update-JsonFile` function checks if a specified JSON file exists and either updates it with new registry data or creates a new file. If the file exists, it reads the current data, appends the new registry data, and writes it back to the file. If the file does not exist, it creates a new file with the provided data. The function handles registry data in a generic array format.

.PARAMETER OutputFile
Specifies the path to the JSON file that should be updated or created. This parameter is mandatory.

.PARAMETER RegistryData
Specifies the new registry data to add to the JSON file. This should be passed as an array. The function will append this data to any existing data in the file, or it will create a new file with this data if the file doesn't exist.

.EXAMPLE
$registryData = @(
    @{ Name = 'HKEY_LOCAL_MACHINE\Software\TestKey'; Value = 'TestValue1' },
    @{ Name = 'HKEY_LOCAL_MACHINE\Software\AnotherKey'; Value = 'TestValue2' }
)
Update-JsonFile -OutputFile 'C:\Temp\RegistryData.json' -RegistryData $registryData

Description:
This example updates the file `RegistryData.json` in `C:\Temp` with the provided `$registryData`. If the file doesn't exist, it will be created.

.EXAMPLE
Update-JsonFile -OutputFile 'C:\Config\Settings.json' -RegistryData @(@{ Name = 'HKEY_CURRENT_USER\Software\MyApp'; Value = 'UserSetting' })

Description:
This command appends the new registry data to the `Settings.json` file located in `C:\Config`. If the file doesn't exist, a new file is created with the registry data.

.OUTPUTS
None. This function writes updated data back to the file specified in the `OutputFile` parameter.

.NOTES
- The function automatically handles appending new data to existing arrays in the JSON file.
- JSON files are written with a depth of 10 to ensure nested objects are properly serialized.

#>

function Update-JsonFile
{
    param (
        [Parameter(Mandatory = $true)]
        [string]$OutputFile,

        [Parameter(Mandatory = $true)]
        [array]$RegistryData  # Generic data for registry keys
    )

    if (Test-Path $OutputFile)
    {
        # Get the existing data and convert it from JSON
        $existingData = Get-Content -Path $OutputFile -Raw | ConvertFrom-Json

        # Ensure existing data is an array, wrap in an array if necessary
        if (-not ($existingData -is [System.Collections.IEnumerable]))
        {
            $existingData = @($existingData)
        }

        # Ensure the existing data is an array of objects
        if ($existingData -isnot [array])
        {
            $existingData = @($existingData)
        }

        # Concatenate the existing data and the new data
        $combinedData = @($existingData + $RegistryData)

        # Write the updated data back to the file
        $combinedData | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputFile -Confirm:$false
    }
    else
    {
        # Create a new JSON file with the provided registry data
        $RegistryData | ConvertTo-Json -Depth 10 | Out-File -FilePath $OutputFile
    }
}
#EndRegion './Private/Helpers/Update-JsonFile.ps1' 77
#Region './Private/New-UserProfileObject.ps1' -1

<#
.SYNOPSIS
    Creates a new UserProfile object.
.DESCRIPTION
    The New-UserProfileObject function creates and returns an instance of the UserProfile class.
    This object contains details about a user profile, including its SID, profile path, whether it is orphaned,
    and other relevant information such as the last logon/logoff times, registry and folder presence, and more.

    This function is useful when managing user profiles across different systems, allowing you to consolidate
    information about profiles and determine their status (e.g., orphaned, special, loaded).

.PARAMETER SID
    The Security Identifier (SID) of the user profile.
.PARAMETER UserName
    The username associated with the profile.
.PARAMETER ProfilePath
    The file path to the user profile folder.
.PARAMETER FolderPath
    The file path to the user profile folder.
.PARAMETER ProfileState
    The current state of the profile (e.g., Active, Inactive).
.PARAMETER HasRegistryEntry
    A boolean value indicating whether the profile has a corresponding registry entry.
.PARAMETER HasUserFolder
    A boolean value indicating whether the profile folder exists on disk.
.PARAMETER LastLogonDate
    The last logon date of the user. Defaults to [datetime]::MinValue if not provided.
.PARAMETER LastLogOffDate
    The last logoff date of the user. Defaults to [datetime]::MinValue if not provided.
.PARAMETER IsOrphaned
    A boolean value indicating whether the profile is orphaned (i.e., exists in the registry but not on disk, or vice versa).
.PARAMETER OrphanReason
    A description of why the profile is considered orphaned, if applicable.
.PARAMETER ComputerName
    The name of the computer where the profile is located.
.PARAMETER IsSpecial
    A boolean value indicating whether the profile is for a special account (e.g., system or default accounts).
.PARAMETER IsLoaded
    A boolean value indicating whether the profile is currently loaded.
.PARAMETER Domain
    The domain to which the user profile belongs.

.OUTPUTS
    Returns an instance of the UserProfile class with the specified details.

.EXAMPLE
    New-UserProfileObject -SID "S-1-5-21-123456789-1001" -UserName "JohnDoe" -ProfilePath "C:\Users\John" `
        -IsOrphaned $true -OrphanReason "MissingRegistryEntry" -ComputerName "Server01" -IsSpecial $false `
        -HasRegistryEntry $true -HasUserFolder $true -IsLoaded $false -ProfileState "Inactive" `
        -LastLogonDate (Get-Date) -LastLogOffDate (Get-Date).AddDays(-1) -Domain "MyDomain"

    Creates a new UserProfile object for the profile associated with the given SID, marking it as orphaned
    with a reason, specifying that it is not loaded, and providing additional details like the last logon/logoff dates.

.NOTES
    This function returns an instance of the UserProfile class, which is used for managing and reporting on
    user profiles across different systems. The class provides detailed information about the profile's status
    and is particularly useful in scenarios where profiles are being audited, removed, or investigated for inconsistencies.
#>


function New-UserProfileObject
{
    [OutputType([UserProfile])]
    param (
        [string]$SID,
        [string]$UserName,
        [string]$ProfilePath,
        [string]$FolderPath,
        [string]$ProfileState,
        [bool]$HasRegistryEntry,
        [bool]$HasUserFolder,
        [datetime]$LastLogonDate = [datetime]::MinValue,
        [datetime]$LastLogOffDate = [datetime]::MinValue,
        [bool]$IsOrphaned,
        [string]$OrphanReason = $null,
        [string]$ComputerName,
        [bool]$IsSpecial,
        [bool]$IsLoaded,
        [string]$Domain
    )

    return [UserProfile]::new(
        $SID,
        $UserName,
        $ProfilePath,
        $FolderPath,
        $ProfileState,
        $HasRegistryEntry,
        $HasUserFolder,
        $LastLogonDate,
        $LastLogOffDate,
        $IsOrphaned,
        $OrphanReason,
        $ComputerName,
        $IsSpecial,
        $IsLoaded,
        $Domain
    )
}
#EndRegion './Private/New-UserProfileObject.ps1' 100
#Region './Private/ProfFolderProcessing/Get-ProcessedUserProfilesFromFolders.ps1' -1

<#
.SYNOPSIS
Processes user profile folders on a specified computer and retrieves user profile details including last logon time, SID, and special account status.

.DESCRIPTION
The `Get-ProcessedUserProfilesFromFolders` function processes a list of user profile folders and retrieves information such as last logon time, user name, SID, and whether the account is marked as special. The function accepts a list of user folders and a computer name to resolve profile data. It handles cases where the SID may not be resolvable and returns a custom object containing the processed profile information.

.PARAMETER ComputerName
Specifies the name of the computer where the user profiles are located. By default, this parameter is set to the current computer (`$env:COMPUTERNAME`). This parameter is mandatory.

.PARAMETER UserFolders
A collection of custom objects representing user folders, each containing `ProfilePath` and `FolderName` properties. This parameter is mandatory and must be supplied with folder details to process.

.EXAMPLE
$folders = @(
    [pscustomobject]@{ ProfilePath = 'C:\Users\TestUser'; FolderName = 'TestUser' },
    [pscustomobject]@{ ProfilePath = 'C:\Users\AdminUser'; FolderName = 'AdminUser' }
)
$ProcessedProfiles = Get-ProcessedUserProfilesFromFolders -ComputerName 'TestComputer' -UserFolders $folders

Description:
This example processes two user profile folders on the 'TestComputer', gathering information about each profile, such as SID, special account status, and last logon date.

.EXAMPLE
$UserFolders = Get-UserProfileFolders -ComputerName 'TestComputer'
$ProcessedProfiles = Get-ProcessedUserProfilesFromFolders -UserFolders $UserFolders

Description:
This example uses a custom function `Get-UserProfileFolders` to retrieve user profile folders from the 'TestComputer' and then processes the profile information for each folder.

.INPUTS
[string] - The name of the computer where the user profiles reside.
[pscustomobject[]] - A list of user profile folders represented as custom objects with `ProfilePath` and `FolderName` properties.

.OUTPUTS
[PSCustomObject] - A processed user profile object containing:
- `UserName`: The username associated with the profile.
- `ComputerName`: The name of the computer where the profile resides.
- `IsSpecial`: Indicates whether the profile belongs to a special account (e.g., system accounts).
- `LastLogonDate`: The last logon time for the user profile.
- `SID`: The Security Identifier (SID) of the profile, if available.
- `ProfilePath`: The path to the user profile folder.
- `ExistsInRegistry`: Boolean indicating whether the profile exists in the registry.
- `HasUserFolder`: Boolean indicating whether the user folder exists on the computer.
- `Domain`: The domain associated with the user, if available.

.NOTES
- This function relies on external functions such as `Resolve-UsernamesToSIDs`, `Get-UserProfileLastUseTimeFromDat`, and `Test-SpecialAccount`.
- Handles exceptions gracefully when unable to resolve SIDs or retrieve account information.
- If no last logon date is found, it defaults to `[DateTime]::MinValue`.
#>

function Get-ProcessedUserProfilesFromFolders
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$ComputerName = $env:COMPUTERNAME,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [pscustomobject[]]$UserFolders
    )

    # Retrieve LastLogonTime information from computer
    $LastLogonTimes = Get-UserProfileLastUseTimeFromDat -ComputerName $ComputerName

    # Process each folder
    $ProcessedProfiles = $UserFolders | ForEach-Object {
        $profilePath = $_.ProfilePath
        $folderName = $_.FolderName
        $TestSpecialParams = @{}
        $TestSpecialParams.Add('ProfilePath', $profilePath)

        # Extract user name from the folder name
        $userName = $folderName -replace '\..*$', ''
        $TestSpecialParams.Add('FolderName', $userName)

        # Get Last Logon Time
        $lastLogOn = $LastLogonTimes | Where-Object { $_.UserPath -eq $profilePath } | Select-Object -ExpandProperty LastLogon

        # Try to resolve SID
        try
        {
            $SID = Resolve-UsernamesToSIDs -Usernames $userName -ComputerName $ComputerName
        }
        catch
        {
            $SID = $null
        }

        # Default values for the existence in the registry and user folder
        $existsInRegistry = $false
        $hasUserFolder = $true

        # If SID is found, gather additional information
        if ($SID)
        {
            try
            {
                $TestSpecialParams.Add('SID', $SID)
                $accountInfo = Get-UserAccountFromSID -SID $SID -ComputerName $ComputerName -WarningAction SilentlyContinue
                $domain = $accountInfo.Domain
                $userName = $accountInfo.Username
            }
            catch
            {
                $domain = $null
                $userName = $null
            }
        }

        # Test if the account is special
        $IsSpecialResults = Test-SpecialAccount @TestSpecialParams
        $IsSpecial = $IsSpecialResults.IsSpecial

        # Return a processed profile object
        [pscustomobject]@{
            UserName         = $userName
            ComputerName     = $ComputerName
            IsSpecial        = $IsSpecial
            LastLogonDate    = if ($lastLogOn) { $lastLogOn } else { [DateTime]::MinValue }
            SID              = $SID
            ProfilePath      = $profilePath
            ExistsInRegistry = $existsInRegistry
            HasUserFolder    = $hasUserFolder
            Domain           = $domain
        }
    }

    # Return processed profiles
    return $ProcessedProfiles
}
#EndRegion './Private/ProfFolderProcessing/Get-ProcessedUserProfilesFromFolders.ps1' 133
#Region './Private/ProfFolderProcessing/Get-UserFolders.ps1' -1

<#
.SYNOPSIS
    Retrieves a list of user profile folders from a specified computer.
.DESCRIPTION
    The Get-UserFolders function scans the user profile directory on the specified computer
    and returns a list of folders that represent user profiles. It determines whether the
    target computer is local or remote and retrieves information such as the folder name,
    profile path, and computer name for each folder.

    If an error occurs during the folder retrieval, the function logs the error and returns
    an empty array.
.PARAMETER ComputerName
    The name of the computer from which to retrieve user profile folders. If not provided,
    defaults to the local computer.
.PARAMETER ProfileFolderPath
    The path to the folder where user profiles are stored. Defaults to "$env:SystemDrive\Users".
    This can be overridden to target a custom directory path for user profiles.
.OUTPUTS
    [PSCustomObject[]]
    An array of custom objects, where each object contains the following properties:
        - FolderName: [string] The name of the user profile folder.
        - ProfilePath: [string] The full path to the user profile folder.
        - ComputerName: [string] The name of the computer from which the user profile folder
          was retrieved.
.EXAMPLE
    Get-UserFolders -ComputerName "Server01" -ProfileFolderPath "D:\UserProfiles"
    Retrieves a list of user profile folders from the "D:\UserProfiles" directory on "Server01".
.EXAMPLE
    Get-UserFolders -ComputerName $env:COMPUTERNAME
    Retrieves a list of user profile folders from the local computer's default user directory.
.EXAMPLE
    Get-UserFolders -ComputerName "RemotePC"
    Retrieves a list of user profile folders from the default user directory on the remote computer
    "RemotePC".
.NOTES
    - If the Get-ChildItem command fails (e.g., due to access issues), the function logs an error
      and returns an empty array.
    - The ProfilePath for local computers is returned as a local path, while for remote computers,
      the folder is first accessed using a UNC path, but the returned ProfilePath is formatted as a
      local path for consistency.
    - Use the optional ProfileFolderPath parameter to target custom directories for user profiles.
#>


function Get-UserFolders
{
    [OutputType([PSCustomObject[]])]
    [CmdletBinding()]
    param (
        [string]$ComputerName = $env:COMPUTERNAME,
        [string]$ProfileFolderPath = $env:WinProfileOps_ProfileFolderPath
    )


    $IsLocal = ($ComputerName -eq $env:COMPUTERNAME)
    $FolderPath = Get-DirectoryPath -BasePath $ProfileFolderPath -ComputerName $ComputerName -IsLocal $IsLocal

    try
    {
        # Get list of all folders in the user profile directory
        $ProfileFolders = Get-ChildItem -Path $FolderPath -Directory | ForEach-Object {
            [PSCustomObject]@{
                FolderName   = $_.Name
                ProfilePath  = Get-DirectoryPath -BasePath $_.FullName -ComputerName $ComputerName -IsLocal $true
                ComputerName = $ComputerName
            }
        }

        return $ProfileFolders
    }
    catch
    {
        # Handle the error when Get-ChildItem fails
        Write-Error "Failed to retrieve folders from '$FolderPath' on '$ComputerName'. Error: $_"
        return @()  # Return an empty array in case of failure
    }
}
#EndRegion './Private/ProfFolderProcessing/Get-UserFolders.ps1' 77
#Region './Private/ProfFolderProcessing/Get-UserProfileLastUseTimeFromDat.ps1' -1

<#
.SYNOPSIS
    Retrieves the last use time of user profiles by reading the UsrClass.dat file on a specified computer.

.DESCRIPTION
    The Get-UserProfileLastUseTimeFromDat function scans the UsrClass.dat files located in user profile directories on a specified computer
    (local or remote) and retrieves the last write time of each file. This last write time is used as an approximation of the user's last logon time.
    The function handles both local and remote computers, returning custom objects with profile details such as username, profile path, and last logon time.

.PARAMETER ComputerName
    The name of the computer from which to retrieve the last use time of user profiles. Defaults to the local computer.

.PARAMETER SystemDrive
    The system drive to search for user profiles, where the UsrClass.dat files are located. Defaults to the system drive of the current computer ($env:SystemDrive).

.OUTPUTS
    PSCustomObject
    Returns an array of custom objects containing the following properties:
    - Success: [bool] Whether the operation was successful.
    - ComputerName: [string] The name of the computer where the profile data was retrieved from.
    - Username: [string] The name of the user associated with the profile (if available).
    - LastLogon: [DateTime] The last write time of the UsrClass.dat file, representing the last logon time.
    - UserPath: [string] The full path to the user profile folder.
    - Error: [string] Error message in case of failure (if applicable).

.EXAMPLE
    Get-UserProfileLastUseTimeFromDat -ComputerName "Server01" -SystemDrive "D:"
    Retrieves the last logon times of user profiles from UsrClass.dat files on "Server01" in the "D:\Users" directory.

.EXAMPLE
    Get-UserProfileLastUseTimeFromDat
    Retrieves the last logon times of user profiles from UsrClass.dat files on the local computer.

.NOTES
    - This function checks if the specified computer is local or remote and adjusts the search path for UsrClass.dat files accordingly.
    - If no UsrClass.dat files are found, a warning is logged, and the function returns an empty result with a success value of $false.
    - If an error occurs during the process, the function logs a warning and returns an error message.

#>


function Get-UserProfileLastUseTimeFromDat
{
    [CmdletBinding()]
    param (
        [string]$ComputerName = $env:COMPUTERNAME,

        [ValidateNotNullOrEmpty()]
        [string]$SystemDrive = $env:SystemDrive
    )

    begin
    {
        Write-Verbose "Starting function Get-UserProfileLastUseTimeFromDat for computer: $ComputerName"

        # Check if we are querying a local or remote computer
        $isLocal = ($ComputerName -eq $env:COMPUTERNAME)

        # Base path to search for UsrClass.dat files in user profiles
        $BasePath = "$SystemDrive\Users\*\AppData\Local\Microsoft\Windows\UsrClass.dat"
        Write-Verbose "Base path for UsrClass.dat: $BasePath"

        $Path = Get-DirectoryPath -BasePath $BasePath -ComputerName $ComputerName -IsLocal $isLocal
        Write-Verbose "Resolved path: $Path"
    }

    process
    {
        try
        {
            # Retrieve the UsrClass.dat file's last write time for each user profile
            Write-Verbose "Retrieving UsrClass.dat files from $Path"
            $profileItems = Get-ChildItem -Path $Path -Force -ErrorAction SilentlyContinue | Sort-Object LastWriteTime

            # Check if any UsrClass.dat files were found
            if (-not $profileItems)
            {
                Write-Warning "No UsrClass.dat files found in path: $Path"
                return [pscustomobject]@{
                    Success      = $false
                    ComputerName = $ComputerName
                    Message      = "No UsrClass.dat files found."
                }
            }

            # Create custom objects for each profile
            $profileItems | ForEach-Object {
                $datFilePath = $_.FullName

                # Extract the user folder path (everything before 'AppData\Local\Microsoft\Windows')
                $userPath = [System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($datFilePath)))))


                # Extract the user name based on the user folder path
                $userName = if ($isLocal)
                {
                    ($userPath).split("\")[2]
                }
                else
                {
                    ($userPath).split("\")[5]
                }

                $lastLogon = $_.LastWriteTime

                [pscustomobject]@{
                    Success      = $true
                    ComputerName = $ComputerName
                    Username     = $userName
                    LastLogon    = $lastLogon
                    UserPath     = (Get-DirectoryPath -BasePath $userPath -IsLocal $true -ComputerName $ComputerName)
                }
            }
        }
        catch
        {
            Write-Warning "An error occurred while processing UsrClass.dat files: $_"
            return [pscustomobject]@{
                Success      = $false
                ComputerName = $ComputerName
                Error        = $_.Exception.Message
                LastLogon    = $null
            }
        }
    }

    end
    {
        Write-Verbose "Completed function Get-UserProfileLastUseTimeFromDat for computer: $ComputerName"
    }
}
#EndRegion './Private/ProfFolderProcessing/Get-UserProfileLastUseTimeFromDat.ps1' 131
#Region './Private/ProfRegProcessing/Get-LogonLogoffDatesFromRegistry.ps1' -1

<#
.SYNOPSIS
    Retrieves logon and logoff dates from a registry subkey.

.DESCRIPTION
    The Get-LogonLogoffDatesFromRegistry function reads the `LocalProfileLoadTimeLow`, `LocalProfileLoadTimeHigh`, `LocalProfileUnloadTimeLow`, and `LocalProfileUnloadTimeHigh`
    values from a specified registry subkey. These values are used to calculate the logon and logoff dates for a user profile.
    If the values are not found, the function uses a default value of `DateTime::MinValue`.

.PARAMETER SubKey
    The registry subkey from which to retrieve the logon and logoff dates. This parameter is mandatory and must be an object of type [Microsoft.Win32.RegistryKey].

.OUTPUTS
    PSCustomObject
    Returns a custom object containing the following properties:
    - Success: [bool] Indicates whether the operation was successful.
    - LogonDate: [DateTime] The calculated logon date (or MinValue if not found).
    - LogoffDate: [DateTime] The calculated logoff date (or MinValue if not found).
    - Error: [string] Error message in case of failure (only present if the operation fails).

.EXAMPLE
    $subKey = Get-RegistrySubKey -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-21-1234567890-1001'
    Get-LogonLogoffDatesFromRegistry -SubKey $subKey
    Retrieves the logon and logoff dates for the specified user profile from the registry.

.EXAMPLE
    Get-LogonLogoffDatesFromRegistry -SubKey $subKey | Format-Table -AutoSize
    Retrieves and formats the logon and logoff dates for easier viewing.

.NOTES
    - This function assumes that the registry values `LocalProfileLoadTimeLow`, `LocalProfileLoadTimeHigh`, `LocalProfileUnloadTimeLow`, and `LocalProfileUnloadTimeHigh`
      exist in the provided subkey. If any of these values are missing, default values will be returned, and a verbose message will be logged.
    - If any error occurs during the process, the function logs a warning and returns an object with `Success = $false` and the error message.
    - The logon and logoff dates are calculated using the FromFileTime method, which converts the registry's 64-bit timestamp into a readable DateTime format.

#>

function Get-LogonLogoffDatesFromRegistry
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Microsoft.Win32.RegistryKey]$SubKey
    )

    begin
    {
        Write-Verbose "Starting function Get-LogonLogoffDatesFromRegistry"
    }

    process
    {
        try
        {
            Write-Verbose "Retrieving logon and logoff dates from subkey: $($SubKey.Name)"

            # Get the LocalProfileLoadTimeLow and LocalProfileLoadTimeHigh values from the registry
            $lowLoadTime = Get-RegistryValue -BaseKey $SubKey -ValueName "LocalProfileLoadTimeLow" -DefaultValue $null
            $highLoadTime = Get-RegistryValue -BaseKey $SubKey -ValueName "LocalProfileLoadTimeHigh" -DefaultValue $null

            # Get the LocalProfileUnloadTimeLow and LocalProfileUnloadTimeHigh values from the registry
            $lowUnloadTime = Get-RegistryValue -BaseKey $SubKey -ValueName "LocalProfileUnloadTimeLow" -DefaultValue $null
            $highUnloadTime = Get-RegistryValue -BaseKey $SubKey -ValueName "LocalProfileUnloadTimeHigh" -DefaultValue $null

            # Initialize logon and logoff dates as MinValue in case they're not found
            $logonDate = [DateTime]::MinValue
            $logoffDate = [DateTime]::MinValue

            # Calculate Logon Date (Load Time)
            if ($lowLoadTime -ne $null -and $highLoadTime -ne $null)
            {
                [uint64]$logonTimestamp = "0X{0:X8}{1:X8}" -f $highLoadTime, $lowLoadTime
                $logonDate = [datetime]::FromFileTime($logonTimestamp)
            }
            else
            {
                Write-Verbose "LocalProfileLoadTimeLow or LocalProfileLoadTimeHigh not found for subkey: $($SubKey.Name). Using MinValue for logon date."
            }

            # Calculate Logoff Date (Unload Time)
            if ($lowUnloadTime -ne $null -and $highUnloadTime -ne $null)
            {
                [uint64]$logoffTimestamp = "0X{0:X8}{1:X8}" -f $highUnloadTime, $lowUnloadTime
                $logoffDate = [datetime]::FromFileTime($logoffTimestamp)
            }
            else
            {
                Write-Verbose "LocalProfileUnloadTimeLow or LocalProfileUnloadTimeHigh not found for subkey: $($SubKey.Name). Using MinValue for logoff date."
            }

            return [pscustomobject]@{
                Success    = $true
                LogonDate  = $logonDate
                LogoffDate = $logoffDate
            }
        }
        catch
        {
            Write-Warning "Error retrieving Logon and Logoff dates from subkey: $($SubKey.Name). Error: $_"
            return [pscustomobject]@{
                Success    = $false
                LogonDate  = $null
                LogoffDate = $null
                Error      = $_.Exception.Message
            }
        }
    }

    end
    {
        Write-Verbose "Completed function Get-LogonLogoffDatesFromRegistry"
    }
}
#EndRegion './Private/ProfRegProcessing/Get-LogonLogoffDatesFromRegistry.ps1' 113
#Region './Private/ProfRegProcessing/Get-ProfilePathFromSID.ps1' -1

<#
.SYNOPSIS
    Retrieves the profile path associated with a specific SID from the registry.

.DESCRIPTION
    The Get-ProfilePathFromSID function retrieves the "ProfileImagePath" registry value for the provided Security Identifier (SID) registry key. The ProfileImagePath indicates the location of the user profile associated with the SID.
    If the ProfileImagePath is not found, the function returns a custom object with the Success property set to $true and the ProfileImagePath property set to $null.
    If an error occurs during retrieval, an error message is logged, and the function returns a custom object with the Success property set to $false and the Error property containing the error message.

.PARAMETER SidKey
    The registry key representing the Security Identifier (SID) from which to retrieve the profile path.
    This parameter is mandatory and must be a valid [Microsoft.Win32.RegistryKey] object.

.OUTPUTS
    [pscustomobject]
    A custom object with the following properties:
    - Success: [bool] Indicates whether the operation was successful.
    - ProfileImagePath: [string] The path to the user profile associated with the SID.
    - Error: [string] (Only present if an error occurs) The error message, if any.

.EXAMPLE
    $sidKey = Get-Item "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-21-1234567890-1234567890-1234567890-1001"
    Get-ProfilePathFromSID -SidKey $sidKey

    Description:
    Retrieves the profile path for the specified SID from the registry key and returns the result.

.EXAMPLE
    $sidKey = Get-Item "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-21-987654321-987654321-987654321-1001"
    $result = Get-ProfilePathFromSID -SidKey $sidKey
    if (-not $result.ProfileImagePath) {
        Write-Host "Profile path not found."
    }

    Description:
    This example retrieves the profile path for a given SID, checks if the ProfileImagePath was found, and prints a message if it is not found.

.NOTES
    This function requires appropriate access to the registry in order to retrieve the ProfileImagePath.
    If the "ProfileImagePath" value does not exist, the function will log a verbose message and return a custom object where ProfileImagePath is $null.
    The function handles errors gracefully by logging a warning and returning a custom object with the error details.
#>

function Get-ProfilePathFromSID
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Microsoft.Win32.RegistryKey]$SidKey
    )

    begin
    {
        Write-Verbose "Starting function Get-ProfilePathFromSID"
    }

    process
    {
        try
        {
            Write-Verbose "Retrieving ProfileImagePath for SID: $($SidKey.Name)"

            # Use Get-RegistryValue to retrieve the "ProfileImagePath"
            $profileImagePath = Get-RegistryValue -BaseKey $SidKey -ValueName "ProfileImagePath"

            if (-not $profileImagePath)
            {
                Write-Verbose "ProfileImagePath not found for SID: $($SidKey.Name)"
            }

            return [pscustomobject]@{
                Success          = $true
                ProfileImagePath = $profileImagePath
            }
        }
        catch
        {
            Write-Warning "Failed to retrieve ProfileImagePath for SID: $($SidKey.Name). Error: $_"
            return [pscustomobject]@{
                Success          = $false
                Error            = $_.Exception.Message
                ProfileImagePath = $null
            }
        }
    }

    end
    {
        Write-Verbose "Completed function Get-ProfilePathFromSID"
    }
}
#EndRegion './Private/ProfRegProcessing/Get-ProfilePathFromSID.ps1' 91
#Region './Private/ProfRegProcessing/Get-ProfileStateFromRegistrySubKey.ps1' -1

<#
.SYNOPSIS
    Retrieves the profile state associated with a specific registry subkey.

.DESCRIPTION
    The Get-ProfileStateFromRegistrySubKey function retrieves the "State" value from the specified registry subkey, which represents the status of a user profile.
    It also decodes the profile state into a human-readable text using the Get-ProfileStateText function.
    If the "State" value cannot be found, the function returns a custom object with Success set to $false and StateText set to "Unknown". In case of errors, the function returns error details and logs a warning.

.PARAMETER SubKey
    The registry subkey that contains the "State" value for the profile.
    This parameter is mandatory and must be a valid [Microsoft.Win32.RegistryKey] object.

.OUTPUTS
    [pscustomobject] - A custom object containing:
    - Success : [bool] Indicates whether the operation was successful.
    - State : [int] The numeric profile state retrieved from the registry.
    - StateText : [string] The decoded profile state as a human-readable string.
    - Error : [string] (Only present if an error occurs) The error message, if any.

.EXAMPLE
    $subKey = Get-Item 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-21-1234567890-1234567890-1234567890-1001'
    Get-ProfileStateFromRegistrySubKey -SubKey $subKey

    Description:
    Retrieves the profile state for the specified SID from the registry and returns the profile state and its decoded text.

.EXAMPLE
    $subKey = Get-Item 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-21-1234567890-1234567890-1234567890-1001'
    $result = Get-ProfileStateFromRegistrySubKey -SubKey $subKey
    if (-not $result.Success) {
        Write-Host "Failed to retrieve profile state."
    }

    Description:
    This example retrieves the profile state and checks if the retrieval was successful. It prints a message if the profile state could not be found.

.NOTES
    - This function depends on Get-RegistryValue to retrieve the "State" value and Get-ProfileStateText to decode the state.
    - Ensure the calling user has appropriate permissions to access the registry.
    - The function handles errors by logging a warning and returning an error message if any exceptions occur.

#>

function Get-ProfileStateFromRegistrySubKey
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Microsoft.Win32.RegistryKey]$SubKey
    )

    begin
    {
        Write-Verbose "Starting function Get-ProfileStateFromRegistrySubKey"
    }

    process
    {
        try
        {
            Write-Verbose "Retrieving 'State' value from subkey: $($SubKey.Name)"

            # Retrieve the 'State' value from the subkey
            $profileState = Get-RegistryValue -BaseKey $SubKey -ValueName "State" -DefaultValue $null

            # Check if the state was found
            if ($profileState -ne $null)
            {
                Write-Verbose "Profile state found: $profileState"
                # Call Get-ProfileStateText to decode the state
                $stateText = Get-ProfileStateText -state $profileState
                return [pscustomobject]@{
                    Success   = $true
                    State     = $profileState
                    StateText = $stateText
                }
            }
            else
            {
                Write-Warning "The 'State' value was not found in subkey: $($SubKey.Name)"
                return [pscustomobject]@{
                    Success   = $false
                    State     = $null
                    StateText = "Unknown"
                }
            }
        }
        catch
        {
            Write-Warning "Error retrieving profile state from subkey: $($SubKey.Name). Error: $_"
            return [pscustomobject]@{
                Success   = $false
                State     = $null
                StateText = "Unknown"
                Error     = $_.Exception.Message
            }
        }
    }

    end
    {
        Write-Verbose "Completed function Get-ProfileStateFromRegistrySubKey"
    }
}
#EndRegion './Private/ProfRegProcessing/Get-ProfileStateFromRegistrySubKey.ps1' 105
#Region './Private/ProfRegProcessing/Get-ProfileStateText.ps1' -1

<#
.SYNOPSIS
    Decodes and returns the profile state text from the given state value.

.DESCRIPTION
    The `Get-ProfileStateText` function decodes the integer `state` value associated with a user's profile and returns a comma-separated string of flags that describe the profile's state.
    The profile state is stored in the registry as a DWORD value under the `HKLM\Software\Microsoft\Windows NT\CurrentVersion\ProfileList\SID` key. Each bit in the state value represents a specific condition or property of the profile.

.PARAMETER state
    The integer state value of the profile to decode. This value can be retrieved from the "State" registry value of a user profile.
    If the state is 0 or less, the function returns "StandardLocal". Otherwise, the function decodes the state using bitwise checks to identify individual flags.

.OUTPUTS
    [string] - A comma-separated string of profile state flags.

.NOTES
    The profile state is stored in the following registry location:
    Key: HKLM\Software\Microsoft\Windows NT\CurrentVersion\ProfileList\SID
    Value: State
    DataType: REG_DWORD

    The state is a sum of the following bitwise flags, represented in hexadecimal (regedit displays them in hex with 0x at the start) and decimal:

    0x0001 (1) = PROFILE_MANDATORY: Profile is mandatory.
    0x0002 (2) = PROFILE_USE_CACHE: Update the locally cached profile.
    0x0004 (4) = PROFILE_NEW_LOCAL: Using a new local profile.
    0x0008 (8) = PROFILE_NEW_CENTRAL: Using a new central profile.
    0x0010 (16) = PROFILE_UPDATE_CENTRAL: Need to update the central profile.
    0x0020 (32) = PROFILE_DELETE_CACHE: Need to delete the cached profile.
    0x0040 (64) = PROFILE_UPGRADE: Need to upgrade the profile.
    0x0080 (128) = PROFILE_GUEST_USER: Using a guest user profile.
    0x0100 (256) = PROFILE_ADMIN_USER: Using an administrator profile.
    0x0200 (512) = DEFAULT_NET_READY: Default net profile is available & ready.
    0x0400 (1024) = PROFILE_SLOW_LINK: Identified a slow network link.
    0x0800 (2048) = PROFILE_TEMP_ASSIGNED: Temporary profile loaded.

    Common state values:
    - 0 = Standard local profile.
    - 5 = Newly-loaded mandatory profile.
    - 256 (0x0100) = Standard administrator local profile.

    reference:
    - https://www.pcreview.co.uk/threads/purpose-of-the-state-key-located-in-users-profiles.2939114/#:~:text=There%20is%20a%20state%20key%20associated
    - https://www.precedence.co.uk/wiki/Support-KB-Windows/ProfileStates

.EXAMPLE
    $profileState = 287
    Get-ProfileStateText -state $profileState

    Output:
    Mandatory,UseCache,NewLocal,NewCentral,UpdateCentral,AdminUser

    Description:
    This example decodes the profile state value `287`, which combines several flags, such as "Mandatory", "UseCache", "NewLocal", "NewCentral", "UpdateCentral", and "AdminUser".

.EXAMPLE
    Get-ProfileStateText -state 0

    Output:
    StandardLocal

    Description:
    This example returns "StandardLocal" for the profile state value `0`.

#>

function Get-ProfileStateText
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [int]$state = 0
    )

    $stateText = @()

    Write-Verbose "Profile state: $state"

    # Special case for state = 0 (Standard local profile)
    if ($state -le 0)
    {
        return "StandardLocal"
    }

    # Bitwise checks for each state flag
    if ($state -band 1)
    {
        $stateText += "Mandatory"
    }
    if ($state -band 2)
    {
        $stateText += "UseCache"
    }
    if ($state -band 4)
    {
        $stateText += "NewLocal"
    }
    if ($state -band 8)
    {
        $stateText += "NewCentral"
    }
    if ($state -band 16)
    {
        $stateText += "UpdateCentral"
    }
    if ($state -band 32)
    {
        $stateText += "DeleteCache"
    }
    if ($state -band 64)
    {
        $stateText += "Upgrade"
    }
    if ($state -band 128)
    {
        $stateText += "GuestUser"
    }
    if ($state -band 256)
    {
        $stateText += "AdminUser"
    }
    if ($state -band 512)
    {
        $stateText += "DefaultNetReady"
    }
    if ($state -band 1024)
    {
        $stateText += "SlowLink"
    }
    if ($state -band 2048)
    {
        $stateText += "TempAssigned"
    }

    # If no flags matched, return "Unknown"
    if (-not $stateText)
    {
        return "Unknown"
    }

    # Return the state descriptions joined by commas
    return $stateText -join ','
}
#EndRegion './Private/ProfRegProcessing/Get-ProfileStateText.ps1' 144
#Region './Private/ProfRegProcessing/Get-UserAccountFromSID.ps1' -1

<#
.SYNOPSIS
    Retrieves the domain and username associated with a given Security Identifier (SID), locally or remotely.

.DESCRIPTION
    The `Get-UserAccountFromSID` function takes a Security Identifier (SID) as input and translates it into a corresponding user account's domain and username. The function can be executed locally or remotely using PowerShell remoting.

    The function uses .NET's `System.Security.Principal.SecurityIdentifier` class to perform the translation and returns a custom object containing the SID, domain, and username. If the SID cannot be translated, it returns null for the domain and username and issues a warning.

.PARAMETER SID
    The Security Identifier (SID) to be translated. This is a required parameter and must not be null or empty. The function supports pipeline input for the SID.

.PARAMETER ComputerName
    The name of the computer to perform the translation. If not specified, the function defaults to the local computer (`$env:COMPUTERNAME`).
    When a remote computer is specified, the function uses `Invoke-Command` to run the translation remotely.

.OUTPUTS
    PSCustomObject - An object with the following properties:
        - SID: The input SID.
        - Domain: The domain of the user account associated with the SID, if found.
        - Username: The username of the user account associated with the SID, if found.

.NOTES
    This function leverages .NET's `System.Security.Principal.SecurityIdentifier` class to translate the SID into a user account in the format `DOMAIN\Username`.
    If the translation fails, a warning is generated, and the function returns null for both the domain and username.

.EXAMPLE
    Get-UserAccountFromSID -SID 'S-1-5-21-1234567890-1234567890-1234567890-1001'

    Output:
    SID Domain Username
    --- ------ --------
    S-1-5-21-1234567890-1234567890-1234567890-1001 DOMAIN User

    Description:
    This example retrieves the domain and username associated with the given SID on the local computer.

.EXAMPLE
    Get-UserAccountFromSID -SID 'S-1-5-21-1234567890-1234567890-1234567890-1001' -ComputerName 'RemoteServer01'

    Output:
    SID Domain Username
    --- ------ --------
    S-1-5-21-1234567890-1234567890-1234567890-1001 DOMAIN User

    Description:
    This example retrieves the domain and username associated with the given SID from the remote computer 'RemoteServer01'.

.EXAMPLE
    'S-1-5-21-1234567890-1234567890-1234567890-1001' | Get-UserAccountFromSID

    Output:
    SID Domain Username
    --- ------ --------
    S-1-5-21-1234567890-1234567890-1234567890-1001 DOMAIN User

    Description:
    This example demonstrates how to pass the SID as pipeline input to retrieve the associated domain and username.

.EXAMPLE
    $sids = @('S-1-5-21-1234567890-1234567890-1234567890-1001', 'S-1-5-21-0987654321-0987654321-0987654321-1002')
    $sids | Get-UserAccountFromSID

    Output:
    SID Domain Username
    --- ------ --------
    S-1-5-21-1234567890-1234567890-1234567890-1001 DOMAIN User
    S-1-5-21-0987654321-0987654321-0987654321-1002 DOMAIN User

    Description:
    This example demonstrates how to pass multiple SIDs through the pipeline and retrieve their associated domain and username.

.EXAMPLE
    Get-UserAccountFromSID -SID 'S-1-5-21-1234567890-1234567890-1234567890-1001'

    Warning:
    Failed to translate SID: S-1-5-21-1234567890-1234567890-1234567890-1001

    Output:
    SID Domain Username
    --- ------ --------
    S-1-5-21-1234567890-1234567890-1234567890-1001 null null

    Description:
    This example demonstrates the behavior of the function when it fails to translate the SID. A warning is issued, and the domain and username are returned as null.
#>

function Get-UserAccountFromSID
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
                if (Validate-SIDFormat -SID $_)
                {
                    $true  # Valid SID format
                }
                else
                {
                    throw "Invalid SID format: $_"
                }
            })]
        [string]$SID,

        [Parameter(Mandatory = $false)]
        [string]$ComputerName = $env:COMPUTERNAME  # Default to the local computer
    )

    begin
    {
    }

    process
    {
        try
        {
            # Define the script block that performs the SID-to-account translation
            $scriptBlock = {
                param ($SID)
                try
                {
                    $ntAccount = New-Object System.Security.Principal.SecurityIdentifier($SID)
                    $userAccount = $ntAccount.Translate([System.Security.Principal.NTAccount])
                    $domain, $username = $userAccount.Value.Split('\', 2)
                    return [pscustomobject]@{
                        Domain   = $domain
                        Username = $username
                    }
                }
                catch
                {
                    Write-Warning "Failed to translate SID: $SID"
                    return [pscustomobject]@{
                        Domain   = $null
                        Username = $null
                    }
                }
            }

            # Invoke the command locally or remotely
            $result = Invoke-Command -ComputerName $ComputerName -ScriptBlock $scriptBlock -ArgumentList $SID

            # Assign the returned result to variables
            $domain = $result.Domain
            $username = $result.Username
        }
        catch
        {
            Write-Warning "Failed to translate SID: $SID"
            $domain = $null
            $username = $null
        }

        [pscustomobject]@{
            SID      = $SID
            Domain   = $domain
            Username = $username
        }
    }

    end
    {
    }
}
#EndRegion './Private/ProfRegProcessing/Get-UserAccountFromSID.ps1' 165
#Region './Private/ProfRegProcessing/Test-FolderExists.ps1' -1

<#
.SYNOPSIS
    Checks if a profile folder exists on a specified computer.
.DESCRIPTION
    The Test-FolderExists function determines whether a given profile folder exists on the specified computer by testing the path.
    If the profile path or computer name is not provided, the function will default to using the local computer.
    In the event of any errors (e.g., invalid paths or inaccessible directories), the function returns $false and logs the error.

.PARAMETER ProfilePath
    The file path of the profile folder to check. This parameter is required. If it is null or empty, the function will return $false.
.PARAMETER ComputerName
    The name of the computer where the profile folder is located. If not provided, the local computer is used by default.
.OUTPUTS
    [bool]
        Returns $true if the folder exists at the specified path, and $false if it does not exist, or if an error occurs during execution.

.EXAMPLE
    Test-FolderExists -ProfilePath "C:\Users\John" -ComputerName "Server01"
    Checks if the folder "C:\Users\John" exists on "Server01".

.EXAMPLE
    Test-FolderExists -ProfilePath "C:\Users\Public"
    Checks if the folder "C:\Users\Public" exists on the local computer (since ComputerName is not specified).

.EXAMPLE
    Test-FolderExists -ProfilePath "C:\InvalidPath" -ComputerName "Server01"
    Returns $false if the specified folder does not exist or if an error occurs while accessing the path.

.NOTES
    The function includes error handling to catch and log any exceptions. In case of an error, the function returns $false.
#>


function Test-FolderExists
{
    [outputType([bool])]
    [cmdletbinding()]
    param (
        [string]$ProfilePath,
        [string]$ComputerName = $env:COMPUTERNAME
    )

    # Check for null or empty ProfilePath
    if (-not $ProfilePath)
    {
        Write-Warning "ProfilePath is null or empty."
        return $false
    }

    # Check for null or empty ComputerName and default to the local computer if it's null
    if (-not $ComputerName)
    {
        Write-Warning "ComputerName is null or empty. Defaulting to the local computer."
        $ComputerName = $env:COMPUTERNAME
    }

    try
    {
        # Determine if the computer is local or remote
        $IsLocal = $ComputerName -eq $env:COMPUTERNAME

        # Get the directory path to check
        $pathToCheck = Get-DirectoryPath -BasePath $ProfilePath -ComputerName $ComputerName -IsLocal $IsLocal

        # Return whether the path exists
        return Test-Path $pathToCheck
    }
    catch [UnauthorizedAccessException]
    {
        Write-Warning "Access denied when testing folder existence for profile: $ProfilePath. Error: $_"
        throw
    }
    catch
    {
        Write-Error "An error occurred: $_"
        return $false
    }
}
#EndRegion './Private/ProfRegProcessing/Test-FolderExists.ps1' 78
#Region './Private/ProfRegProcessing/Test-SpecialAccount.ps1' -1

<#
.SYNOPSIS
    Tests if a user profile is considered a special or default account.
.DESCRIPTION
    The Test-SpecialAccount function checks whether the profile is classified as a special or default account.
    It compares the folder name, Security Identifier (SID), and profile path against predefined lists of ignored
    accounts, SIDs, and paths that are typically used to identify system, default, or service accounts.
    If the profile matches any entry from these predefined configurations, it is marked as a special account.

    The function utilizes a configuration file (in .psd1 format) containing the lists of ignored accounts, SIDs,
    and paths. If the profile matches any of the entries in the configuration file, the account is flagged as special.

.PARAMETER FolderName
    The name of the profile folder being tested (e.g., "DefaultAppPool", "JohnDoe").
.PARAMETER SID
    The Security Identifier (SID) of the profile being tested (e.g., "S-1-5-18", "S-1-5-21-123456789-1001").
.PARAMETER ProfilePath
    The file system path of the profile being tested (e.g., "C:\Users\JohnDoe" or "C:\WINDOWS\system32\config\systemprofile").
.PARAMETER ConfigFilePath
    The path to the configuration file (.psd1) that contains the lists of ignored accounts, SIDs, and paths.
    Defaults to "$PSScriptRoot\Data\WinProfileOpsConfig.psd1".

    The configuration file is expected to contain the following sections:
    - IgnoredAccounts: An array of folder names representing special accounts.
    - IgnoredSIDs: An array of SIDs representing special accounts.
    - IgnoredPaths: An array of file path patterns (wildcards are supported) representing special profile paths.

.EXAMPLE
    Test-SpecialAccount -FolderName "DefaultAppPool" -SID "S-1-5-18" -ProfilePath "C:\WINDOWS\system32\config\systemprofile"
    Checks if the profile with folder name "DefaultAppPool", SID "S-1-5-18", and profile path "C:\WINDOWS\system32\config\systemprofile"
    is classified as a special account based on predefined rules.

.EXAMPLE
    Test-SpecialAccount -FolderName "JohnDoe" -SID "S-1-5-21-123456789-1001" -ProfilePath "C:\Users\JohnDoe"
    Tests whether the profile "JohnDoe" is a special account. Since it doesn't match any predefined special account rules,
    it returns that the profile is not special.

.EXAMPLE
    Test-SpecialAccount -FolderName "Administrator" -SID "S-1-5-21-1234567890-1001" -ProfilePath "C:\Users\Administrator" `
    -ConfigFilePath "C:\CustomConfig\SpecialAccounts.psd1"
    Uses a custom configuration file to test whether the "Administrator" account is considered special.

.NOTES
    This function returns a custom object that includes whether the account is special, along with the folder name,
    SID, and profile path. The result can be used to filter out special accounts when performing user profile audits.

    If the configuration file is not found or cannot be loaded, the function will throw an error.

.OUTPUTS
    PSCustomObject
    Returns a custom object with the following properties:
    - Success: Boolean value indicating whether the function executed successfully.
    - IsSpecial: Boolean value indicating whether the profile is considered a special or default account.
    - FolderName: The folder name of the tested profile.
    - SID: The Security Identifier (SID) of the tested profile.
    - ProfilePath: The profile's file path.
    - Error: (Optional) Contains error message if an issue occurred during processing.

.LINK
    Get-Help about_Profiles
    Get-Help about_Security_Identifiers
#>

function Test-SpecialAccount
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$FolderName,

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

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

        [string]$ConfigFilePath = "$PSScriptRoot\Data\WinProfileOpsConfig.psd1"  # Path to the config file
    )

    begin
    {
        Write-Verbose "Starting function Test-SpecialAccount"

        # Load the configuration from the .psd1 file
        if (Test-Path $ConfigFilePath)
        {
            Write-Verbose "Loading configuration file from $ConfigFilePath"
            $config = Import-PowerShellDataFile -Path $ConfigFilePath
        }
        else
        {
            throw "Config file not found at path '$ConfigFilePath'."
        }
    }

    process
    {
        try
        {
            # Check if the account is special based on the folder name, SID, or profile path
            $isSpecialAccount = ($config.IgnoredAccounts -contains $FolderName) -or ($config.IgnoredSIDs -contains $SID)
            Write-Verbose "Checking if account is special based on folder name or SID"

            # Check for wildcard matches in paths
            $isSpecialPath = $false
            foreach ($ignoredPath in $config.IgnoredPaths)
            {
                if ($ProfilePath -like $ignoredPath)
                {
                    Write-Verbose "Profile path matches ignored path pattern: $ignoredPath"
                    $isSpecialPath = $true
                    break
                }
            }

            # Return whether the account or path is special
            $result = $isSpecialAccount -or $isSpecialPath

            return [pscustomobject]@{
                Success     = $true
                IsSpecial   = $result
                FolderName  = $FolderName
                SID         = $SID
                ProfilePath = $ProfilePath
            }
        }
        catch
        {
            Write-Warning "An error occurred while testing if the account is special: $_"
            return [pscustomobject]@{
                Success   = $false
                IsSpecial = $false
                Error     = $_.Exception.Message
            }
        }
    }

    end
    {
        Write-Verbose "Completed function Test-SpecialAccount"
    }
}
#EndRegion './Private/ProfRegProcessing/Test-SpecialAccount.ps1' 142
#Region './Private/ProfRegProcessing/Validate-SIDFormat.ps1' -1

<#
.SYNOPSIS
    Validates whether a given string follows the correct SID (Security Identifier) format.

.DESCRIPTION
    The Validate-SIDFormat function checks if a given string matches the standard SID format.
    SIDs typically start with 'S-1-' followed by a series of digits separated by hyphens.
    This function returns $true if the SID format is valid and $false if it is not.

.PARAMETER SID
    The SID string to validate. This should follow the typical format: 'S-1-' followed by
    a series of digits and hyphens.

.OUTPUTS
    [bool]
    Returns $true if the SID format is valid; otherwise, returns $false.

.EXAMPLE
    PS> Validate-SIDFormat -SID 'S-1-5-18'
    True

    This example checks if the SID 'S-1-5-18' is valid.

.EXAMPLE
    PS> Validate-SIDFormat -SID 'Invalid-SID'
    WARNING: Invalid SID format encountered: 'Invalid-SID'.
    False

    This example demonstrates how the function handles an invalid SID format by returning $false
    and issuing a warning.

.NOTES

.LINK
    https://docs.microsoft.com/en-us/windows/win32/secauthz/security-identifiers

#>

function Validate-SIDFormat
{
    param (
        [OutPutType([bool])]
        [CmdletBinding()]
        [Parameter(Mandatory = $true)]
        [string]$SID
    )

    # Regular expression pattern for validating the SID format
    $sidPattern = '^S-1-\d+(-\d+)+$'

    if ($SID -notmatch $sidPattern)
    {
        Write-Warning "Invalid SID format encountered: '$SID'."
        return $false
    }

    return $true
}
#EndRegion './Private/ProfRegProcessing/Validate-SIDFormat.ps1' 58
#Region './Private/RemoveProfileReg/Backup-RegistryKeyForSID.ps1' -1

<#
.SYNOPSIS
Backs up a registry key associated with a specific SID to a specified directory.

.DESCRIPTION
The `Backup-RegistryKeyForSID` function creates a backup of the registry key associated with the provided SID from a remote or local machine. It ensures that the backup directory exists before proceeding, creates a JSON representation of the registry data, and appends the backup to an existing JSON file.

.PARAMETER SID
Specifies the Security Identifier (SID) for which the registry key backup is created.

.PARAMETER BaseKey
Specifies the base registry key under which the SID subkey exists.

.PARAMETER RegBackUpDirectory
Specifies the directory where the registry backup will be saved.

.PARAMETER ComputerName
Specifies the name of the computer from which the registry key is being backed up.

.EXAMPLE
Backup-RegistryKeyForSID -SID 'S-1-5-21-...' -BaseKey $RegistryKey -RegBackUpDirectory 'C:\Backups' -ComputerName 'Server01'

Description:
Backs up the registry key for the specified SID from Server01 to the 'C:\Backups' directory.

.OUTPUTS
Boolean indicating success or failure.

.NOTES
This function relies on helper functions like `New-DirectoryIfNeeded` and `New-RegistryKeyValuesObject` to handle registry operations.
#>


function Backup-RegistryKeyForSID
{
    param (
        [Parameter(Mandatory = $true)]
        [string]$SID,

        [Parameter(Mandatory = $true)]
        [Microsoft.Win32.RegistryKey]$BaseKey,

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

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

    try
    {
        # Ensure the backup directory exists
        $directoryCreated = New-DirectoryIfNeeded -Directory $RegBackUpDirectory

        # Check if directory creation failed
        if (-not $directoryCreated)
        {
            Write-Error "Error creating or accessing backup directory: $RegBackUpDirectory"
            return $false
        }

        # Backup the registry key associated with the SID
        $RegBackUpObject = New-RegistryKeyValuesObject -RegistryKey $BaseKey -ComputerName $ComputerName -SubKeyName $SID
        $RegBackUpObjectJson = $RegBackUpObject.psobject.copy()
        $RegBackUpObjectJson.BackUpDate = $RegBackUpObject.BackUpDate.tostring("o")

        # Update the backup JSON file with the registry data
        Update-JsonFile -OutputFile "$RegBackUpDirectory\RegBackUp.json" -RegistryData $RegBackUpObjectJson

        return $true
    }
    catch
    {
        Write-Error "Error backing up registry for SID $SID`: $_"
        return $false
    }
}
#EndRegion './Private/RemoveProfileReg/Backup-RegistryKeyForSID.ps1' 77
#Region './Private/RemoveProfileReg/Confirm-ProfileRemoval.ps1' -1

<#
.SYNOPSIS
Verifies whether a registry key for a specific SID has been successfully removed.

.DESCRIPTION
The `Confirm-ProfileRemoval` function checks whether the registry key associated with the specified SID still exists. If the key no longer exists, the function returns `$true`; otherwise, it returns `$false`.

.PARAMETER SID
Specifies the Security Identifier (SID) whose registry key removal is being confirmed.

.PARAMETER BaseKey
Specifies the base registry key under which the SID subkey exists.

.EXAMPLE
Confirm-ProfileRemoval -SID 'S-1-5-21-...' -BaseKey $RegistryKey

Description:
Checks if the registry key for the specified SID has been successfully removed.

.OUTPUTS
Boolean indicating whether the registry key was removed.
#>


function Confirm-ProfileRemoval
{
    param (
        [string]$SID,
        [Microsoft.Win32.RegistryKey]$BaseKey
    )

    try
    {
        return ($BaseKey.GetSubKeyNames() -notcontains $SID)
    }
    catch
    {
        Write-Error "Error verifying profile removal for SID $SID`: $_"
        return $false
    }
}
#EndRegion './Private/RemoveProfileReg/Confirm-ProfileRemoval.ps1' 41
#Region './Private/RemoveProfileReg/Invoke-UserProfileRegRemoval.ps1' -1

<#
.SYNOPSIS
Removes user profile registry entries from local or remote computers, with optional confirmation.

.DESCRIPTION
The `Invoke-UserProfileRegRemoval` function processes user profiles for removal based on Security Identifiers (SIDs). It retrieves profiles from a specified registry path and profile folder, performs an audit, and optionally prompts for confirmation before removal. The `Force` switch can bypass the confirmation prompt, and the `AuditOnly` switch allows auditing without any removal action.

If the registry key cannot be opened or the audit fails, the function terminates early to prevent further processing.

.PARAMETER ComputerName
Specifies the name of the computer where the profile removal is executed. This can be a local or remote machine.

.PARAMETER SID
Specifies the Security Identifier (SID) of the user profile to remove. This parameter accepts pipeline input, allowing multiple SIDs to be processed sequentially.

.PARAMETER RegistryPath
Specifies the registry path where user profile information is stored. For example, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList`.

.PARAMETER ProfileFolderPath
Specifies the folder path where user profile data is stored. For example, `C:\Users`.

.PARAMETER RegistryHive
Specifies the registry hive (e.g., HKLM for HKEY_LOCAL_MACHINE or HKCU for HKEY_CURRENT_USER) under which the profile keys are located.

.PARAMETER Force
Forces the removal of profiles without prompting for confirmation. When this switch is used, profiles are removed without any user interaction.

.PARAMETER AuditOnly
Performs an audit without removing any profiles. The audit results are output to the pipeline, and no changes are made to the registry.

.PARAMETER Confirm
If specified, the user is prompted for confirmation before removing each profile. The prompt is skipped if `Force` or `AuditOnly` switches are used.

.EXAMPLE
Get-UserProfiles | Invoke-UserProfileRegRemoval -ComputerName 'Server01' -RegistryPath 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -ProfileFolderPath 'C:\Users' -RegistryHive 'LocalMachine' -Force

Description:
Removes all user profiles from the registry on Server01 without prompting for confirmation, as the `Force` switch is used.

.EXAMPLE
Get-UserProfiles | Invoke-UserProfileRegRemoval -ComputerName 'Server02' -RegistryPath 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -ProfileFolderPath 'C:\Users' -RegistryHive 'LocalMachine' -AuditOnly

Description:
Performs an audit of the user profiles on Server02, but does not remove any profiles. The audit results are output to the pipeline.

.EXAMPLE
'S-1-5-21-12345' | Invoke-UserProfileRegRemoval -ComputerName 'Server03' -RegistryPath 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -ProfileFolderPath 'C:\Users' -RegistryHive 'LocalMachine'

Description:
Processes the specified SID ('S-1-5-21-12345') for removal on Server03. If `Confirm` is specified, the user is prompted before the profile is removed.

.NOTES
- This function uses pipeline input to process multiple SIDs.
- The function handles both local and remote computers.
- Errors during registry key access or audit failure result in early termination.
- If special system profiles are detected during the audit, they can be skipped based on the implementation of the audit function.
#>

function Invoke-UserProfileRegRemoval
{
    [CmdletBinding()]
    param (
        [string]$ComputerName,

        # Accept pipeline input for each SID
        [Parameter(ValueFromPipeline = $true)]
        [string]$SID,

        [string]$RegistryPath,
        [string]$ProfileFolderPath,
        [Microsoft.Win32.RegistryHive]$RegistryHive,
        [switch]$Force,
        [switch]$AuditOnly,
        [bool]$Confirm
    )

    Begin
    {
        # Initialize a flag to determine if processing should continue
        $continueProcessing = $true

        try
        {
            # Try to open the registry key
            $BaseKey = Open-RegistryKey -ComputerName $ComputerName -RegistryHive $RegistryHive -RegistryPath $RegistryPath -ErrorAction SilentlyContinue

            # Check if the registry key is valid
            if (-not $BaseKey)
            {
                throw "Failed to open registry key on computer $ComputerName"
            }

            # Perform the audit if the BaseKey is valid
            $userProfileAudit = Invoke-UserProfileAudit -ComputerName $ComputerName -ProfileFolderPath $ProfileFolderPath -IgnoreSpecial

            if (-not $userProfileAudit)
            {
                throw "Failed to audit user profiles on computer $ComputerName"
            }

        }
        catch
        {
            # Catch any exceptions that occur during the process
            Write-Error $_.Exception.Message
            $continueProcessing = $false  # Set the flag to prevent further processing
            return  # Exit the function early if an error occurs
        }
    }

    Process
    {
        # Only proceed if the flag allows processing
        if ($continueProcessing)
        {
            # Process each SID as it flows through the pipeline
            $SelectedProfile = Resolve-UserProfileForDeletion -SID $SID -AuditResults $userProfileAudit -ComputerName $ComputerName

            if ($SelectedProfile -is [ProfileDeletionResult])
            {
                # Output the ProfileDeletionResult directly to the pipeline
                $SelectedProfile
            }
            else
            {
                # Skip confirmation if AuditOnly is used
                if (-not $AuditOnly)
                {
                    if (-not $Force -and (ShouldContinueWrapper -Context $PSCmdlet -QueryMessage "Do you want to delete SID $SID from $($SelectedProfile.ComputerName)?" -CaptionMessage "Confirm Deletion"))
                    {
                        $result = Remove-UserProfileRegistryEntry -SelectedProfile $SelectedProfile -BaseKey $BaseKey -AuditOnly:$AuditOnly
                        $result
                    }
                    elseif ($Force)
                    {
                        $result = Remove-UserProfileRegistryEntry -SelectedProfile $SelectedProfile -BaseKey $BaseKey -AuditOnly:$AuditOnly
                        $result
                    }
                }
                else
                {
                    # Just process without confirmation
                    $result = Remove-UserProfileRegistryEntry -SelectedProfile $SelectedProfile -BaseKey $BaseKey -AuditOnly:$AuditOnly
                    $result
                }
            }
        }
    }

    End
    {
        # Clean up resources
        if ($BaseKey)
        {
            $BaseKey.Dispose()
        }
    }
}
#EndRegion './Private/RemoveProfileReg/Invoke-UserProfileRegRemoval.ps1' 158
#Region './Private/RemoveProfileReg/New-ProfileDeletionResult.ps1' -1

<#
.SYNOPSIS
Creates a new `ProfileDeletionResult` object with details of a user profile deletion.

.DESCRIPTION
The `New-ProfileDeletionResult` function generates a new object representing the outcome of a user profile deletion operation. This object can include details such as the SID, profile path, deletion status, and computer name.

.PARAMETER SID
Specifies the Security Identifier (SID) of the user profile.

.PARAMETER ProfilePath
Specifies the path to the user profile that was deleted (optional).

.PARAMETER DeletionSuccess
Specifies whether the profile deletion was successful.

.PARAMETER DeletionMessage
Provides a message regarding the profile deletion result.

.PARAMETER ComputerName
Specifies the name of the computer from which the profile was removed.

.EXAMPLE
New-ProfileDeletionResult -SID 'S-1-5-21-...' -DeletionSuccess $true -DeletionMessage 'Profile removed successfully.'

Description:
Creates a `ProfileDeletionResult` object indicating that the profile for the specified SID was successfully removed.

.OUTPUTS
ProfileDeletionResult object containing the details of the deletion operation.
#>

function New-ProfileDeletionResult
{
    [CmdletBinding(DefaultParameterSetName = 'Minimal')]
    param (
        # SID is mandatory in all parameter sets
        [Parameter(Mandatory = $true, ParameterSetName = 'Full')]
        [Parameter(Mandatory = $true, ParameterSetName = 'SuccessOnly')]
        [Parameter(Mandatory = $true, ParameterSetName = 'Minimal')]
        [string]$SID,

        # Full parameter set properties
        [Parameter(Mandatory = $false, ParameterSetName = 'Full')]
        [string]$ProfilePath =$null,

        [Parameter(Mandatory = $true, ParameterSetName = 'Full')]
        [Parameter(Mandatory = $true, ParameterSetName = 'SuccessOnly')]
        [bool]$DeletionSuccess,

        [Parameter(Mandatory = $true, ParameterSetName = 'Full')]
        [string]$DeletionMessage,

        [Parameter(Mandatory = $true, ParameterSetName = 'Full')]
        [string]$ComputerName
    )

    switch ($PSCmdlet.ParameterSetName)
    {
        'Full'
        {
            return [ProfileDeletionResult]::new($SID, $ProfilePath, $DeletionSuccess, $DeletionMessage, $ComputerName)
        }
        'SuccessOnly'
        {
            return [ProfileDeletionResult]::new($SID, $DeletionSuccess)
        }
        'Minimal'
        {
            return [ProfileDeletionResult]::new($SID)
        }
    }
}
#EndRegion './Private/RemoveProfileReg/New-ProfileDeletionResult.ps1' 73
#Region './Private/RemoveProfileReg/PromptForConfirmation.ps1' -1

<#
.SYNOPSIS
Prompts the user for confirmation before proceeding with a deletion operation.

.DESCRIPTION
The `PromptForConfirmation` function asks the user to confirm before performing a deletion operation on a specified computer's registry. If the `AuditOnly` flag is specified, the prompt is skipped. If `Confirm` is set to `$true`, the function displays a confirmation message with details about the number of items to delete and the target computer. The user response is handled by the `ShouldContinueWrapper` function, which manages the confirmation prompt.

.PARAMETER ComputerName
Specifies the name of the computer where the deletion operation will take place.

.PARAMETER ItemCount
Specifies the number of profiles to delete from the computer's registry. This is displayed in the confirmation message.

.PARAMETER AuditOnly
If this switch is specified, the function will skip the confirmation prompt and proceed without making any changes. This is typically used for audit or dry-run scenarios.

.PARAMETER Confirm
If this switch is specified, the function will always prompt the user for confirmation before proceeding.

.PARAMETER context
Specifies the execution context, typically used to access methods like `ShouldContinue` for the confirmation prompt.

.EXAMPLE
PromptForConfirmation -ComputerName 'Server01' -ItemCount 5 -Confirm

Description:
Prompts the user to confirm the deletion of 5 profiles from the registry of 'Server01'. If the user confirms, the function returns `$true`; otherwise, it returns `$false`.

.EXAMPLE
PromptForConfirmation -ComputerName 'Server02' -ItemCount 10 -AuditOnly

Description:
Skips the confirmation prompt since the `AuditOnly` switch is used, and returns `$true` to proceed with the audit operation.

.NOTES
The function assumes that `ShouldContinueWrapper` is available to handle the actual confirmation prompt.
#>


function PromptForConfirmation
{
    param (
        [string]$ComputerName,
        [int]$ItemCount,
        [switch]$AuditOnly,
        [switch]$Confirm,
        $context
    )

    # Skip prompt if in AuditOnly mode
    if ($AuditOnly)
    {
        return $true
    }


    # Always prompt unless Force is specified or Confirm is explicitly set to false
    if ($Confirm -eq $true)
    {
        $QueryMessage = "Are you sure you want to delete $ItemCount profiles from $ComputerName's registry?"
        $CaptionMessage = "Confirm Deletion"

        # Use the ShouldContinueWrapper to handle the prompt
        return (ShouldContinueWrapper -Context $context -QueryMessage $QueryMessage -CaptionMessage $CaptionMessage)
    }

    return $true # Proceed if Force is used or if AuditOnly is true
}
#EndRegion './Private/RemoveProfileReg/PromptForConfirmation.ps1' 68
#Region './Private/RemoveProfileReg/Remove-ProfileRegistryKey.ps1' -1

<#
.SYNOPSIS
Removes a registry key associated with a specific SID.

.DESCRIPTION
The `Remove-ProfileRegistryKey` function deletes the registry key associated with a specified SID. If the operation fails, an error is logged.

.PARAMETER SID
Specifies the Security Identifier (SID) whose registry key is being removed.

.PARAMETER BaseKey
Specifies the base registry key under which the SID subkey exists.

.EXAMPLE
Remove-ProfileRegistryKey -SID 'S-1-5-21-...' -BaseKey $RegistryKey

Description:
Removes the registry key for the specified SID from the provided base key.

.OUTPUTS
Boolean indicating whether the registry key was successfully removed.
#>


function Remove-ProfileRegistryKey
{
    param (
        [string]$SID,
        [Microsoft.Win32.RegistryKey]$BaseKey
    )

    try
    {
        Remove-RegistrySubKey -ParentKey $BaseKey -SubKeyName $SID -ThrowOnMissingSubKey $false -Confirm:$false
        return $true
    }
    catch
    {
        Write-Error "Error removing registry key for SID $SID`: $_"
        return $false
    }
}
#EndRegion './Private/RemoveProfileReg/Remove-ProfileRegistryKey.ps1' 42
#Region './Private/RemoveProfileReg/Remove-UserProfileRegistryEntry.ps1' -1

<#
.SYNOPSIS
Removes a user profile registry entry and backs up the registry data before deletion.

.DESCRIPTION
The `Remove-UserProfileRegistryEntry` function removes a user profile from the Windows registry. Before removal, it backs up the registry data to a specified directory. The function also supports audit mode, where no deletion occurs but an audit log is created.

.PARAMETER SelectedProfile
Specifies the user profile object representing the profile to be deleted.

.PARAMETER BaseKey
Specifies the base registry key under which the profile's SID subkey exists.

.PARAMETER AuditOnly
If specified, the function will only perform an audit and will not delete the registry entry.

.EXAMPLE
Remove-UserProfileRegistryEntry -SelectedProfile $Profile -BaseKey $RegistryKey -AuditOnly

Description:
Performs an audit of the profile without deleting it from the registry.

.OUTPUTS
ProfileDeletionResult object indicating the outcome of the deletion or audit operation.
#>


function Remove-UserProfileRegistryEntry
{
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param (
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [UserProfile]$SelectedProfile,
        [Microsoft.Win32.RegistryKey]$BaseKey,
        [switch]$AuditOnly
    )

    Process
    {
        # Prepare the deletion result parameters
        $deletionResultParams = @{
            SID             = $SelectedProfile.SID
            ProfilePath     = $SelectedProfile.ProfilePath
            ComputerName    = $SelectedProfile.ComputerName
            DeletionSuccess = $false
            DeletionMessage = "Profile not removed."
        }

        # Check if BaseKey is null
        if (-not $BaseKey)
        {
            $deletionResultParams.DeletionMessage = "Failed: BaseKey is null, cannot remove the profile."
            New-ProfileDeletionResult @deletionResultParams
            return  # Return early to stop further processing
        }

        # If in audit mode, output an audit-only result directly to the pipeline and return
        if ($AuditOnly)
        {
            $deletionResultParams.DeletionSuccess = $true
            $deletionResultParams.DeletionMessage = "Audit only, no deletion performed."
            New-ProfileDeletionResult @deletionResultParams
            return  # Return to allow pipeline to continue with the next item
        }

        # Determine backup directory
        $RegBackUpDirectory = Get-DirectoryPath -basePath $env:WinProfileOps_RegBackUpDirectory -ComputerName $SelectedProfile.ComputerName -IsLocal ($SelectedProfile.ComputerName -eq $env:COMPUTERNAME)

        # Backup the registry key, output failure message if backup fails and skip further processing
        if (-not (Backup-RegistryKeyForSID -SID $SelectedProfile.SID -BaseKey $BaseKey -RegBackUpDirectory $RegBackUpDirectory -ComputerName $SelectedProfile.ComputerName))
        {
            $deletionResultParams.DeletionMessage = "Failed to backup profile."
            New-ProfileDeletionResult @deletionResultParams
            return  # Return to allow pipeline to continue with the next item
        }

        # Remove the registry key, output failure message if removal fails
        if (-not (Remove-ProfileRegistryKey -SID $SelectedProfile.SID -BaseKey $BaseKey))
        {
            $deletionResultParams.DeletionMessage = "Failed to remove profile registry key."
            New-ProfileDeletionResult @deletionResultParams
            return  # Return to allow pipeline to continue with the next item
        }

        # Verify the removal and update the result
        if (Confirm-ProfileRemoval -SID $SelectedProfile.SID -BaseKey $BaseKey)
        {
            $deletionResultParams.DeletionSuccess = $true
            $deletionResultParams.DeletionMessage = "Profile removed successfully."
        }
        else
        {
            $deletionResultParams.DeletionMessage = "Profile removal verification failed."
        }

        # Output the final deletion result
        New-ProfileDeletionResult @deletionResultParams
    }
}
#EndRegion './Private/RemoveProfileReg/Remove-UserProfileRegistryEntry.ps1' 99
#Region './Private/RemoveProfileReg/Resolve-UsernamesToSIDs.ps1' -1

<#
.SYNOPSIS
Resolves a list of usernames to their corresponding Security Identifiers (SIDs).

.DESCRIPTION
The `Resolve-UsernamesToSIDs` function resolves each provided username to its corresponding Security Identifier (SID) on a specified computer or the local machine. It uses the `Get-SIDFromUsername` function, which can resolve usernames to SIDs either locally or remotely. For each username, the function attempts to resolve the username on the specified computer. If a username cannot be resolved, a warning is logged, and the function continues processing the next username.

.PARAMETER Usernames
Specifies an array of usernames to resolve to SIDs. This parameter is mandatory.

.PARAMETER ComputerName
Specifies the name of the computer on which to resolve the usernames to SIDs. If not provided, the function defaults to the local computer.

.EXAMPLE
Resolve-UsernamesToSIDs -Usernames 'user1', 'user2'

Description:
Resolves the SIDs for 'user1' and 'user2' on the local computer.

.EXAMPLE
Resolve-UsernamesToSIDs -Usernames 'user1', 'user2' -ComputerName 'Server01'

Description:
Resolves the SIDs for 'user1' and 'user2' on the remote computer 'Server01'.

.OUTPUTS
Array of custom objects containing the username and the corresponding SID. If a username cannot be resolved, the SID will be null, and a warning will be logged.

.NOTES
This function supports resolving SIDs on remote computers using the `ComputerName` parameter.
#>

function Resolve-UsernamesToSIDs
{
    param (
        [string[]]$Usernames,
        [string]$ComputerName = $env:COMPUTERNAME
    )

    $SIDs = @()

    foreach ($Username in $Usernames)
    {
        try
        {
            $SID = Get-SIDFromUsername -Username $Username -ComputerName $ComputerName
        }
        catch {}

        # Ensure $SID is not $null before adding to $SIDs array
        if ($SID)
        {
            $SIDs += $SID
        }
        else
        {
            Write-Verbose "Could not resolve SID for username $Username."
        }
    }

    return $SIDs
}
#EndRegion './Private/RemoveProfileReg/Resolve-UsernamesToSIDs.ps1' 62
#Region './Private/RemoveProfileReg/Resolve-UserProfileForDeletion.ps1' -1

<#
.SYNOPSIS
Finds the user profile for a specific SID in an audit result.

.DESCRIPTION
The `Resolve-UserProfileForDeletion` function searches through audit results to find the profile associated with a given SID. If the profile is not found, a warning is logged, and a `ProfileDeletionResult` is returned indicating failure.

.PARAMETER SID
Specifies the Security Identifier (SID) of the profile to search for.

.PARAMETER AuditResults
Specifies the audit results to search for the profile.

.PARAMETER ComputerName
Specifies the name of the computer where the profile is located.

.EXAMPLE
Resolve-UserProfileForDeletion -SID 'S-1-5-21-...' -AuditResults $AuditResults -ComputerName 'Server01'

Description:
Finds the user profile associated with the specified SID in the audit results for Server01.

.OUTPUTS
UserProfile or ProfileDeletionResult object.
#>

function Resolve-UserProfileForDeletion
{
    param (
        [Parameter(Mandatory = $true)]
        [string]$SID, # The SID to search for
        [Parameter(Mandatory = $false)]
        [UserProfile[]]$AuditResults, # The audit results
        [Parameter(Mandatory = $true)]
        [string]$ComputerName       # The target computer name
    )

    # Find the corresponding user profile from the audit
    $SelectedProfile = $AuditResults | Where-Object { $_.SID -eq $SID }

    # Handle cases where profile is not found
    if ($null -eq $SelectedProfile)
    {
        # Determine if it's an invalid SID or just not found
        $message = if (Validate-SIDFormat -SID $SID)
        {
            "Profile not found"
            Write-Warning "Profile not found for SID: $SID on $ComputerName."
        }
        else
        {
            "Invalid SID format encountered"
            Write-Warning "Invalid SID format encountered: $SID on $ComputerName."
        }

        # Return a ProfileDeletionResult if the profile is not found or invalid
        return New-ProfileDeletionResult -SID $SID -ProfilePath $null -DeletionSuccess $false -DeletionMessage $message -ComputerName $ComputerName
    }

    if ($SelectedProfile.IsLoaded -eq $true)
    {
        Write-Warning "Profile is currently loaded and cannot be deleted: $SID on $ComputerName."
        return New-ProfileDeletionResult -SID $SID -ProfilePath $SelectedProfile.ProfilePath -DeletionSuccess $false -DeletionMessage "Profile is currently loaded and cannot be deleted" -ComputerName $ComputerName

    }

    # If profile is found, return the UserProfile object
    return $SelectedProfile
}
#EndRegion './Private/RemoveProfileReg/Resolve-UserProfileForDeletion.ps1' 69
#Region './Private/UserProfileAudit/ConvertTo-UserProfile.ps1' -1

<#
.SYNOPSIS
    Converts profile registry items into user profile objects.

.DESCRIPTION
    The `ConvertTo-UserProfile` function takes a collection of profile registry items and converts them into custom user profile objects.
    The function evaluates each profile based on certain conditions (e.g., missing profile path, missing user folder, access denied, etc.)
    and determines whether the profile is orphaned. The output is a `UserProfile` object with properties such as `SID`, `UserName`, `ProfilePath`,
    `ProfileState`, `IsOrphaned`, and more. The function also supports different views, including an `OrphanDetails` view for additional detail.

.PARAMETER ProfileRegistryItems
    A collection of profile registry items to be processed. Each item must contain properties such as `SID`, `UserName`, `ProfilePath`,
    `HasRegistryEntry`, `HasUserFolder`, and others used to determine the profile's state. This parameter is mandatory.

.PARAMETER View
    Specifies the view of the output object. By default, it outputs in the `Default` view. If `OrphanDetails` is selected, additional type
    information is included in the output for orphaned profiles. This parameter is optional and accepts values of "Default" and "OrphanDetails".

.EXAMPLE
    $profiles = Get-ProfileRegistryItems -ComputerName "Server01"
    $userProfiles = $profiles | ConvertTo-UserProfile

    Description:
    This example retrieves profile registry items from "Server01" and converts them into user profile objects.

.EXAMPLE
    $profiles = Get-ProfileRegistryItems -ComputerName "Server01"
    $orphanProfiles = $profiles | ConvertTo-UserProfile -View "OrphanDetails"

    Description:
    This example retrieves profile registry items and converts them into user profile objects, including detailed information for orphaned profiles
    using the `OrphanDetails` view.

.INPUTS
    [psobject[]] - Profile registry items as input objects.

.OUTPUTS
    [UserProfile] - A custom user profile object with properties such as:
      - `SID`: Security Identifier of the user profile.
      - `UserName`: The username associated with the profile.
      - `ProfilePath`: The path to the profile folder.
      - `ProfileState`: The state of the profile (e.g., Active, Orphaned).
      - `IsOrphaned`: Indicates whether the profile is considered orphaned.
      - `OrphanReason`: Reason for the profile being orphaned.
      - `HasRegistryEntry`: Indicates if the profile has a registry entry.
      - `HasUserFolder`: Indicates if the profile has a user folder.
      - `LastLogonDate`: The last logon date for the profile.
      - `LastLogOffDate`: The last logoff date for the profile.
      - `ComputerName`: The name of the computer where the profile resides.
      - `IsSpecial`: Indicates if the profile belongs to a special account (e.g., system accounts).
      - `IsLoaded`: Indicates if the profile is currently loaded.
      - `Domain`: The domain associated with the user, if applicable.

.NOTES
    The function uses a switch statement to evaluate different conditions for orphaning a profile. If a profile lacks both a registry entry and a user folder, it is marked as orphaned. The function outputs a custom `UserProfile` object with properties relevant to user profile management, such as last logon/logoff dates, orphan status, and more.

#>

function ConvertTo-UserProfile
{
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [psobject[]]$ProfileRegistryItems,

        [Parameter()]
        [ValidateSet("Default", "OrphanDetails")]
        [string]$View = "Default"
    )

    process
    {
        foreach ($profileItem in $ProfileRegistryItems)
        {
            $IsOrphaned = $false
            $OrphanReason = $null
            $ErrorAccess = $profileItem.ErrorAccess

            switch ($true)
            {
                # Case: Both ProfilePath and User Folder are missing
                { -not $profileItem.ProfilePath -and -not $profileItem.HasUserFolder }
                {
                    $IsOrphaned = $true
                    $OrphanReason = "MissingProfileImagePathAndFolder"
                    break
                }

                # Case: ProfilePath is missing (but folder might exist)
                { -not $profileItem.ProfilePath -and $profileItem.HasRegistryEntry }
                {
                    $IsOrphaned = $true
                    $OrphanReason = "MissingProfileImagePath"
                    break
                }

                # Case: User folder is missing, but ProfilePath exists
                { -not $profileItem.HasUserFolder }
                {
                    $IsOrphaned = $true
                    $OrphanReason = "MissingFolder"
                    break
                }

                # Case: Registry entry exists but folder is missing (tracked by FolderMissing flag)
                { $profileItem.HasRegistryEntry -and $profileItem.FolderMissing }
                {
                    $IsOrphaned = $true
                    $OrphanReason = "FolderMissingOnDisk"
                    break
                }

                # Case: Access denied for special account
                { ($profileItem.HasUserFolder) -and $ErrorAccess -and ($profileItem.IsSpecial) }
                {
                    $IsOrphaned = $false
                    $OrphanReason = "AccessDenied"
                    break
                }

                # Case: Registry entry is missing but it's not a special account
                { -not $profileItem.HasRegistryEntry -and -not $profileItem.IsSpecial }
                {
                    $IsOrphaned = $true
                    $OrphanReason = "MissingRegistryEntry"
                    break
                }

                # Default case
                default
                {
                    $IsOrphaned = $false
                    $OrphanReason = $null
                }
            }

            $LastLogonDate = if ($profileItem.LastLogonDate) { $profileItem.LastLogonDate } else { [datetime]::MinValue }
            $LastLogOffDate = if ($profileItem.LastLogOffDate) { $profileItem.LastLogOffDate } else { [datetime]::MinValue }

            # Using New-Object to create the UserProfile object
            $userProfile = New-Object -TypeName "UserProfile" -ArgumentList (
                $profileItem.SID,
                $profileItem.UserName,
                $profileItem.ProfilePath,
                $profileItem.FolderPath,
                $profileItem.ProfileState,
                $profileItem.HasRegistryEntry,
                $profileItem.HasUserFolder,
                $LastLogonDate,
                $LastLogOffDate,
                $IsOrphaned,
                $OrphanReason,
                $profileItem.ComputerName,
                $profileItem.IsSpecial,
                $profileItem.IsLoaded,
                $profileItem.Domain
            )

            if ($View -eq "OrphanDetails")
            {
                $userProfile.psobject.TypeNames.Insert(0, 'UserProfile.OrphanDetails')
            }

            # Output the UserProfile object
            $userProfile
        }
    }
}
#EndRegion './Private/UserProfileAudit/ConvertTo-UserProfile.ps1' 167
#Region './Private/UserProfileAudit/Get-MergeKey.ps1' -1

<#
.SYNOPSIS
    Generates a composite key based on both the SID and ProfilePath.

.DESCRIPTION
    The Get-MergeKey function creates a unique composite key by concatenating
    the Security Identifier (SID) and the Profile Path. This composite key
    is useful for merging or comparing user profile data from different sources,
    such as folders and registry entries, ensuring a consistent key structure.

.PARAMETER SID
    The Security Identifier (SID) of the user or profile. This should be
    a string representing the unique SID for a user profile.

.PARAMETER ProfilePath
    The path to the user profile. This is the file system path where the
    profile is stored, such as "C:\Users\JohnDoe".

.EXAMPLE
    Get-MergeKey -SID 'S-1-5-21-1234567890-1234567890-1234567890-1001' -ProfilePath 'C:\Users\JohnDoe'

    Returns:
    'S-1-5-21-1234567890-1234567890-1234567890-1001|C:\Users\JohnDoe'

    This example shows how the function returns a composite key based on the SID and ProfilePath.

.EXAMPLE
    Get-MergeKey -SID 'S-1-5-21-1234567890-1234567890-1234567890-1001' -ProfilePath ''

    Returns:
    'S-1-5-21-1234567890-1234567890-1234567890-1001|'

    This example demonstrates that the function can handle cases where the ProfilePath is an empty string.

.NOTES
    This function is designed to create a consistent key format when working with
    user profile data. It is commonly used in scenarios where both the SID and
    ProfilePath are required to uniquely identify a user profile.

#>

function Get-MergeKey
{
    param(
        [string]$SID,
        [string]$ProfilePath
    )

    # Generate a composite key based on both SID and ProfilePath
    return "$SID|$ProfilePath"
}
#EndRegion './Private/UserProfileAudit/Get-MergeKey.ps1' 51
#Region './Private/UserProfileAudit/Join-UserProfiles.ps1' -1

<#
.SYNOPSIS
    Merges user profile information from folder profiles and registry profiles.

.DESCRIPTION
    The Join-UserProfiles function takes folder profiles and registry profiles as input and merges them based on SID and ProfilePath.
    It prioritizes the registry profile data when both folder and registry profiles are present for the same user.
    The merged data is returned as an array of objects, sorted by SID and ProfilePath.

.PARAMETER FolderProfiles
    An array of custom objects representing folder profiles. These objects should include properties like SID, UserName, ProfilePath, LastLogonDate, HasUserFolder, ComputerName, IsSpecial, Domain, and ErrorAccess.
    This parameter is optional, but at least one of FolderProfiles or RegistryProfiles must be provided. If both FolderProfiles and RegistryProfiles are empty, the function throws an error.

.PARAMETER RegistryProfiles
    An array of custom objects representing registry profiles. These objects should include properties like SID, UserName, ProfilePath, LastLogonDate, LastLogOffDate, ProfileState, IsLoaded, and HasRegistryEntry.
    This parameter is optional, but at least one of RegistryProfiles or FolderProfiles must be provided. If both RegistryProfiles and FolderProfiles are empty, the function throws an error.

.EXAMPLE
    $folderProfiles = @(
        [pscustomobject]@{ SID = 'S-1-5-21-123456789-123456789-123456789-1001'; UserName = 'John'; ProfilePath = 'C:\Users\John'; LastLogonDate = (Get-Date); HasUserFolder = $true; ComputerName = 'PC01'; IsSpecial = $false; Domain = 'DOMAIN'; ErrorAccess = $false }
    )
    $registryProfiles = @(
        [pscustomobject]@{ SID = 'S-1-5-21-123456789-123456789-123456789-1001'; UserName = 'John'; ProfilePath = 'C:\Users\John'; LastLogonDate = (Get-Date).AddHours(-5); LastLogOffDate = (Get-Date).AddHours(-2); ProfileState = 1; IsLoaded = $false; HasRegistryEntry = $true }
    )
    Join-UserProfiles -FolderProfiles $folderProfiles -RegistryProfiles $registryProfiles

    This example merges the folder profile and registry profile for the user 'John' based on the SID.

.EXAMPLE
    $folderProfiles = @(
        [pscustomobject]@{ SID = 'S-1-5-21-123456789-123456789-123456789-1002'; UserName = 'Jane'; ProfilePath = 'C:\Users\Jane'; LastLogonDate = (Get-Date); HasUserFolder = $true; ComputerName = 'PC02'; IsSpecial = $false; Domain = 'DOMAIN'; ErrorAccess = $false }
    )
    $registryProfiles = @()
    Join-UserProfiles -FolderProfiles $folderProfiles -RegistryProfiles $registryProfiles

    This example merges the folder profile for the user 'Jane' and since no registry profile is provided, the resulting object will only include the folder profile information.

.EXAMPLE
    $registryProfiles = @(
        [pscustomobject]@{ SID = 'S-1-5-21-123456789-123456789-123456789-1003'; UserName = 'Admin'; ProfilePath = 'C:\Users\Admin'; LastLogonDate = (Get-Date).AddHours(-3); LastLogOffDate = (Get-Date).AddHours(-1); ProfileState = 1; IsLoaded = $true; HasRegistryEntry = $true }
    )
    Join-UserProfiles -RegistryProfiles $registryProfiles

    This example merges only the registry profile for the user 'Admin' since no folder profile is provided.

.NOTES
    If both FolderProfiles and RegistryProfiles are empty, the function will throw an error.
    If only one of FolderProfiles or RegistryProfiles is provided, it will return the data for the non-empty input.

#>

function Join-UserProfiles
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [pscustomobject[]]$FolderProfiles,

        [Parameter(Mandatory = $false)]
        [pscustomobject[]]$RegistryProfiles
    )

    # Check if both FolderProfiles and RegistryProfiles are empty, throw an error if true
    if (($FolderProfiles.Count -eq 0) -and ($RegistryProfiles.Count -eq 0))
    {
        throw "Both FolderProfiles and RegistryProfiles are empty. Cannot proceed."
    }

    # Create a hashtable to store the merged profiles by SID
    $MergedProfiles = @{}
    $unresolvedIndex = 1  # Counter for unresolved profiles

    # Process folder profiles if they exist
    if ($FolderProfiles.Count -ne 0)
    {
        foreach ($folderProfile in $FolderProfiles)
        {
            $mergeKey = $folderProfile.SID

            # Use a placeholder key if SID is missing
            if (-not $mergeKey)
            {
                $mergeKey = "UnknownSID-$unresolvedIndex"
                $unresolvedIndex++
            }

            # Add folder profile data into the hashtable
            $MergedProfiles[$mergeKey] = [pscustomobject]@{
                SID              = $folderProfile.SID
                UserName         = $folderProfile.UserName
                FolderPath       = $folderProfile.ProfilePath  # Capture FolderPath for folder profile
                ProfilePath      = $null  # Keep ProfilePath empty until registry item is processed
                LastLogonDate    = $folderProfile.LastLogonDate
                HasUserFolder    = $folderProfile.HasUserFolder
                ComputerName     = $folderProfile.ComputerName
                IsSpecial        = $folderProfile.IsSpecial
                Domain           = $folderProfile.Domain
                HasRegistryEntry = $false  # Will be updated if registry entry exists
                ProfileState     = $null   # To be updated by registry profile if present
                IsLoaded         = $null   # To be updated by registry profile if present
                LastLogOffDate   = $null   # To be updated by registry profile if present
                ErrorAccess      = $folderProfile.ErrorAccess
                FolderMissing    = $false  # To track if folder is missing but exists in registry
                UnresolvedSID    = (-not $folderProfile.SID)  # Flag if SID is unresolved
            }
        }
    }

    # Process registry profiles, even if FolderProfiles is empty
    foreach ($registryProfile in $RegistryProfiles)
    {
        $mergeKey = $registryProfile.SID

        # Use a placeholder key if SID is missing
        if (-not $mergeKey)
        {
            $mergeKey = "UnknownSID-$unresolvedIndex"
            $unresolvedIndex++
        }

        if ($MergedProfiles.ContainsKey($mergeKey))
        {
            # We found a matching SID, now merge the profile details
            $MergedProfiles[$mergeKey].HasRegistryEntry = $true
            $MergedProfiles[$mergeKey].ProfileState = $registryProfile.ProfileState
            $MergedProfiles[$mergeKey].IsLoaded = $registryProfile.IsLoaded
            $MergedProfiles[$mergeKey].LastLogOffDate = $registryProfile.LastLogOffDate
            $MergedProfiles[$mergeKey].LastLogonDate = $registryProfile.LastLogonDate

            # If ProfilePath is null in the registry, use FolderPath, otherwise use ProfilePath
            if (-not $registryProfile.ProfilePath)
            {
                $MergedProfiles[$mergeKey].ProfilePath = $null
            }
            else
            {
                if (-not $registryProfile.HasUserFolder)
                {
                    $MergedProfiles[$mergeKey].ProfilePath = $registryProfile.ProfilePath
                    $MergedProfiles[$mergeKey].FolderMissing = $true
                }
                else
                {
                    $MergedProfiles[$mergeKey].ProfilePath = $registryProfile.ProfilePath
                }
            }

            # Override additional details from the registry
            $MergedProfiles[$mergeKey].UserName = $registryProfile.UserName
            $MergedProfiles[$mergeKey].Domain = $registryProfile.Domain
            $MergedProfiles[$mergeKey].IsSpecial = $registryProfile.IsSpecial
            $MergedProfiles[$mergeKey].HasUserFolder = $MergedProfiles[$mergeKey].HasUserFolder
            $MergedProfiles[$mergeKey].UnresolvedSID = (-not $registryProfile.SID)
        }
        else
        {
            # Add registry profile directly if no folder profile match is found or FolderProfiles is empty
            $MergedProfiles[$mergeKey] = [pscustomobject]@{
                SID              = $registryProfile.SID
                UserName         = $registryProfile.UserName
                FolderPath       = $null  # No folder match found
                ProfilePath      = $registryProfile.ProfilePath
                LastLogonDate    = $registryProfile.LastLogonDate
                HasUserFolder    = $registryProfile.HasUserFolder
                ComputerName     = $registryProfile.ComputerName
                IsSpecial        = $registryProfile.IsSpecial
                Domain           = $registryProfile.Domain
                HasRegistryEntry = $true
                ProfileState     = $registryProfile.ProfileState
                IsLoaded         = $registryProfile.IsLoaded
                LastLogOffDate   = $registryProfile.LastLogOffDate
                ErrorAccess      = $registryProfile.ErrorAccess
                FolderMissing    = $false  # Assume folder is not missing if HasUserFolder is true
                UnresolvedSID    = (-not $registryProfile.SID)  # Flag if SID is unresolved
            }
        }
    }

    # Return the merged profiles as an array
    return $MergedProfiles.Values | Sort-Object SID
}
#EndRegion './Private/UserProfileAudit/Join-UserProfiles.ps1' 181
#Region './Private/UserProfileAudit/Test-ComputerReachability.ps1' -1

<#
.SYNOPSIS
    Tests whether a specified computer is reachable by performing a network ping.

.DESCRIPTION
    The Test-ComputerReachability function checks whether a specified computer is reachable by using a ping
    test. If the computer is unreachable, the function logs a warning message and returns `$false`. If the
    computer is reachable, it returns `$true`.

.PARAMETER ComputerName
    The name of the computer to test for network reachability.

.OUTPUTS
    [bool]
    Returns `$true` if the computer is reachable, `$false` if the computer is offline or unreachable.

.EXAMPLE
    Test-ComputerReachability -ComputerName "Server01"

    Tests the reachability of "Server01" and returns `$true` if it is reachable, `$false` otherwise.

.NOTES
    This function uses Test-ComputerPing to perform the ping test. If the computer is offline or unreachable,
    a warning is logged and the function returns `$false`.
#>

function Test-ComputerReachability
{
    [OutputType ([bool])]
    param (
        [string]$ComputerName = $env:COMPUTERNAME
    )

    if ($ComputerName -eq $null)
    {
        Write-Warning "No computer name provided."
        return $false
    }

    if (-not (Test-ComputerPing -ComputerName $ComputerName))
    {
        Write-Warning "Computer '$ComputerName' is offline or unreachable."
        return $false
    }
    return $true
}
#EndRegion './Private/UserProfileAudit/Test-ComputerReachability.ps1' 46
#Region './Public/Get-OrphanedProfiles.ps1' -1

<#
.SYNOPSIS
    Retrieves orphaned user profiles from a specified computer.
.DESCRIPTION
    The Get-OrphanedProfiles function scans both the user profile folders and registry on a specified computer and returns profiles that are considered orphaned. Orphaned profiles are those that exist either in the file system but not in the registry, or in the registry but no longer have a corresponding folder in the file system.
.PARAMETER ComputerName
    The name of the computer from which to retrieve orphaned user profiles. Defaults to the local computer.
.PARAMETER ProfileFolderPath
    The path to the folder where user profiles are stored. Defaults to "$env:SystemDrive\Users".
.PARAMETER IgnoreSpecial
    Switch to ignore special or default profiles during the profile retrieval process.
.OUTPUTS
    [UserProfile[]]
    Returns an array of UserProfile objects that represent the profiles found during the audit. Each UserProfile object contains the following properties:
    - SID: [string] The security identifier of the user profile.
    - ProfilePath: [string] The file system path of the user profile.
    - IsOrphaned: [bool] Whether the profile is considered orphaned.
    - OrphanReason: [string] The reason for orphaned status if applicable.
    - ComputerName: [string] The name of the computer where the audit was performed.
    - IsSpecial: [bool] Whether the profile is considered special or a system account.
.EXAMPLE
    Get-OrphanedProfiles -ComputerName "Server01"
    Retrieves all orphaned user profiles from both the file system and registry on "Server01".
.EXAMPLE
    Get-OrphanedProfiles -ProfileFolderPath "D:\UserProfiles" -IgnoreSpecial
    Retrieves orphaned user profiles from the specified folder and ignores special or default profiles.
.NOTES
    This function filters orphaned profiles based on the results of the Invoke-UserProfileAudit function.
#>

function Get-OrphanedProfiles
{
    [CmdletBinding()]
    [OutputType([UserProfile[]])]
    param (
        [ValidateNotNullOrEmpty()]
        [string]$ComputerName = $env:COMPUTERNAME,
        [string]$ProfileFolderPath = $env:WinProfileOps_ProfileFolderPath,
        [switch]$IgnoreSpecial
    )


    try
    {
        # Call Invoke-UserProfileAudit to get all profiles
        $allProfiles = Invoke-UserProfileAudit -ComputerName $ComputerName -ProfileFolderPath $ProfileFolderPath -IgnoreSpecial:$IgnoreSpecial

        # Filter to return only orphaned profiles
        $orphanedProfiles = $allProfiles | Where-Object { $_.IsOrphaned }

        # Handle the case where no orphaned profiles are found
        if (-not $orphanedProfiles)
        {
            Write-Verbose "No orphaned profiles found on computer '$ComputerName'."
            return @()  # Return an empty array
        }
    }
    catch
    {
        Write-Error "An error occurred while retrieving orphaned profiles: $_"
        return
    }

    return $orphanedProfiles
}
#EndRegion './Public/Get-OrphanedProfiles.ps1' 65
#Region './Public/Get-UserProfilesFromFolders.ps1' -1

<#
.SYNOPSIS
    Retrieves user profile folders from a specified computer.
.DESCRIPTION
    The Get-UserProfilesFromFolders function scans the user profile directory on the specified
    computer and returns information about the user profile folders found. This function is useful
    for identifying profile folders stored on disk, which may or may not match entries in the registry.
    The function checks if the specified computer is online before attempting to retrieve the profile folders.
    If the computer is unreachable, it logs a warning and returns an empty array.
    If no profile folders are found, another warning is logged and an empty array is returned.
.PARAMETER ComputerName
    The name of the computer from which to retrieve user profile folders. If null or empty, it defaults to the local computer.
.PARAMETER ProfileFolderPath
    The path to the folder where user profiles are stored. Defaults to "$env:WinProfileOps_ProfileFolderPath".
.OUTPUTS
    PSCustomObject[]
    Returns an array of custom objects representing the user profile folders found. Each object contains:
    - FolderName: The name of the user profile folder.
    - ProfilePath: The full path to the user profile folder.
    - ComputerName: The name of the computer where the profile folder was retrieved from.
.EXAMPLE
    Get-UserProfilesFromFolders -ComputerName "Server01" -ProfileFolderPath "D:\UserProfiles"
    Retrieves user profile folders from the "D:\UserProfiles" directory on "Server01".
.EXAMPLE
    Get-UserProfilesFromFolders
    Retrieves user profile folders from the default "$env:WinProfileOps_ProfileFolderPath" directory on the local computer.
.NOTES
    - This function checks if the specified computer is reachable using Test-ComputerPing. If the computer is offline or unreachable,
      it logs a warning and returns an empty array.
    - If no user profile folders are found in the specified directory, another warning is logged, and an empty array is returned.
    - In case of any errors during the retrieval process, the function handles exceptions, logs an error, and returns an empty array.
#>


function Get-UserProfilesFromFolders
{
    [OutputType([PSCustomObject[]])]
    [CmdletBinding()]
    param (
        [string]$ComputerName = $env:COMPUTERNAME,
        [string]$ProfileFolderPath = $env:WinProfileOps_ProfileFolderPath
    )

    try
    {

        if (-not $ComputerName -or $null -eq $ComputerName)
        {
            $ComputerName = $env:COMPUTERNAME
        }

        # Test if the computer is online before proceeding
        if (-not (Test-ComputerPing -ComputerName $ComputerName))
        {
            Write-Warning "Computer '$ComputerName' is offline or unreachable."
            return @()  # Return an empty array
        }

        # Get user folders and return them
        $UserFolders = Get-UserFolders -ComputerName $ComputerName -ProfileFolderPath $ProfileFolderPath -ErrorAction Stop

        if (-not $UserFolders)
        {
            Write-Warning "No user profile folders found in '$ProfileFolderPath' on computer '$ComputerName'."
            return @()  # Return an empty array
        }

        Get-ProcessedUserProfilesFromFolders -UserFolders $UserFolders -ComputerName $ComputerName 

    }
    catch
    {
        Write-Error "Error retrieving user folders from '$ProfileFolderPath' on computer '$ComputerName'. Error: $_"
        return @()  # Return an empty array in case of failure
    }
}
#EndRegion './Public/Get-UserProfilesFromFolders.ps1' 76
#Region './Public/Get-UserProfilesFromRegistry.ps1' -1

<#
.SYNOPSIS
    Retrieves user profiles from the registry of a specified computer.
.DESCRIPTION
    The Get-UserProfilesFromRegistry function queries the ProfileList registry key on the specified computer
    and returns information about the user profiles found in the registry. This includes details such as the
    security identifier (SID) and the profile path. The function checks if the computer is reachable before
    proceeding with the operation. If the computer is unreachable, a warning is logged, and an empty array is returned.
    If an error occurs while accessing the registry, the function logs an error and returns an empty array.
.PARAMETER ComputerName
    The name of the computer from which to retrieve user profiles. If null or empty, it defaults to the local computer.
.PARAMETER RegistryPath
    The path to the registry key containing the user profiles. Defaults to the value of the `$Env:WinProfileOps_RegistryPath` environment variable.
.PARAMETER RegistryHive
    The hive in the registry where the profile list is located (e.g., 'HKLM'). Defaults to the value of the `$Env:WinProfileOps_RegistryHive` environment variable.
.OUTPUTS
    PSCustomObject[]
    Returns an array of custom objects representing the user profiles found in the registry. Each object contains:
    - SID: [string] The security identifier (SID) of the user profile.
    - ProfilePath: [string] The path to the user profile.
    - ComputerName: [string] The name of the computer where the profile was retrieved.
.EXAMPLE
    Get-UserProfilesFromRegistry -ComputerName "Server01"
    Retrieves the user profiles from the registry on "Server01".
.EXAMPLE
    Get-UserProfilesFromRegistry
    Retrieves the user profiles from the local computer's registry.
.NOTES
    - This function first checks if the specified computer is reachable using Test-ComputerPing. If the computer
      is offline or unreachable, a warning is logged and an empty array is returned.
    - If there is an issue accessing the registry or no profiles are found, an error is logged, and the function
      returns an empty array.
    - If no `ComputerName` is specified, it defaults to the local computer.
#>


function Get-UserProfilesFromRegistry
{
    [OutputType([PSCustomObject[]])]
    [CmdletBinding()]
    param (
        [string] $ComputerName = $env:COMPUTERNAME,
        [string]$RegistryPath = $Env:WinProfileOps_RegistryPath,
        [string]$RegistryHive = $env:WinProfileOps_RegistryHive
    )

    try
    {

        if (-not $ComputerName -or $null -eq $ComputerName)
        {
            $ComputerName = $env:COMPUTERNAME
        }

        # Test if the computer is online before proceeding
        if (-not (Test-ComputerPing -ComputerName $ComputerName))
        {
            Write-Warning "Computer '$ComputerName' is offline or unreachable."
            return @()  # Return an empty array
        }

        # Get registry profiles and return them
        Get-ProfileRegistryItems -ComputerName $ComputerName -RegistryPath $RegistryPath -RegistryHive $RegistryHive -WarningAction SilentlyContinue

    }
    catch
    {
        Write-Error "Error accessing registry profiles on computer '$ComputerName'. Error: $_"
        return @()  # Return an empty array in case of failure
    }
}
#EndRegion './Public/Get-UserProfilesFromRegistry.ps1' 71
#Region './Public/Invoke-UserProfileAudit.ps1' -1

<#
.SYNOPSIS
    Audits user profiles on a specified computer by comparing profiles found in the registry and file system.

.DESCRIPTION
    The Invoke-UserProfileAudit function retrieves user profile information from both the file system (user folders)
    and the registry on a specified computer. It compares these profiles to identify orphaned profiles,
    profiles that exist in one location but not the other, and optionally ignores special or default profiles.
    This function is useful for auditing user profiles and detecting inconsistencies across the registry and file system.

    The function supports remote auditing and pipeline input for multiple computer names. It ensures that only
    reachable computers are processed and handles invalid or inaccessible registry paths gracefully. Additionally,
    special profiles, such as system accounts, can be excluded from the results if the `IgnoreSpecial` switch is used.

.PARAMETER ComputerName
    The name of the computer from which to audit user profiles. Defaults to the local computer.

.PARAMETER ProfileFolderPath
    The path to the folder where user profiles are stored. Defaults to the folder defined by the
    "$env:WinProfileOps_ProfileFolderPath" environment variable, or "$env:SystemDrive\Users" if not specified.

.PARAMETER RegistryPath
    The registry path where user profiles are stored. Defaults to the path defined by "$Env:WinProfileOps_RegistryPath".

.PARAMETER RegistryHive
    The registry hive to be used for gathering profile data. Defaults to the value of "$env:WinProfileOps_RegistryHive".

.PARAMETER IgnoreSpecial
    Switch to ignore special or default profiles (such as system accounts or service accounts) during the audit process.

.OUTPUTS
    [UserProfile[]]
    Returns an array of UserProfile objects that represent the profiles found during the audit. Each UserProfile object contains the following properties:
    - SID: [string] The security identifier of the user profile.
    - ProfilePath: [string] The file system path of the user profile.
    - IsOrphaned: [bool] Whether the profile is considered orphaned.
    - OrphanReason: [string] The reason for orphaned status if applicable.
    - ComputerName: [string] The name of the computer where the audit was performed.
    - IsSpecial: [bool] Whether the profile is considered special or a system account.

.EXAMPLE
    Invoke-UserProfileAudit -ComputerName "Server01"
    Audits all user profiles from both the file system and the registry on "Server01".

.EXAMPLE
    Invoke-UserProfileAudit -ProfileFolderPath "D:\UserProfiles" -IgnoreSpecial
    Audits user profiles from the "D:\UserProfiles" folder on the local computer, ignoring special or default profiles.

.EXAMPLE
    'Server01', 'Server02' | Invoke-UserProfileAudit
    Performs a profile audit for both Server01 and Server02 via pipeline input.

.EXAMPLE
    Get-AllUserProfiles -ComputerName "Server01"
    This alias performs the same audit as Invoke-UserProfileAudit, returning all user profiles for "Server01".

.NOTES
    This function performs a profile audit by comparing user profiles in the file system and registry.
    It supports pipeline input for multiple computer names and includes an alias `Get-AllUserProfiles`.

    If the `IgnoreSpecial` switch is used, special profiles such as system or default profiles will be excluded
    from the audit results. Invalid registry or folder paths are handled gracefully by writing error messages.

    The function also ensures that computers are reachable before performing any profile audits, and will skip
    unreachable computers without further processing. Profiles can be retrieved for local or remote computers.
#>

function Invoke-UserProfileAudit
{
    [OutputType([UserProfile[]])]
    [CmdletBinding()]
    [Alias("Get-AllUserProfiles")]
    param (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [string]$ComputerName = $env:COMPUTERNAME,

        [string]$ProfileFolderPath = $env:WinProfileOps_ProfileFolderPath,

        [string]$RegistryPath = $Env:WinProfileOps_RegistryPath,
        [string]$RegistryHive = $env:WinProfileOps_RegistryHive,

        [switch]$IgnoreSpecial
    )

    begin
    {
        $AllProfiles = @()
        if ($null -eq $ComputerName)
        {
            $ComputerName = $env:COMPUTERNAME
        }
    }

    process
    {
        if (-not (Test-ComputerReachability -ComputerName $ComputerName))
        {
            Write-Warning "Computer '$ComputerName' is not reachable."
            return
        }

        try
        {
            # Step 1: Gather profile information from the registry
            $RegistryProfiles = Get-UserProfilesFromRegistry -ComputerName $ComputerName -RegistryHive $RegistryHive -RegistryPath $RegistryPath
            $FolderProfiles = Get-UserProfilesFromFolders -ComputerName $ComputerName -ProfileFolderPath $ProfileFolderPath

            $JoinedProfiles = Join-UserProfiles -FolderProfiles $FolderProfiles -RegistryProfiles $RegistryProfiles


            # Step 5: Optionally filter out special profiles if IgnoreSpecial is set
            if ($IgnoreSpecial)
            {
                $JoinedProfiles = $JoinedProfiles | Where-Object { -not $_.IsSpecial }
            }

            $JoinedProfiles | ConvertTo-UserProfile -View OrphanDetails

        }
        catch
        {
            Write-Error "Error processing profiles for computer '$ComputerName'. Error: $_"
        }
    }

    end
    {

    }
}
#EndRegion './Public/Invoke-UserProfileAudit.ps1' 130
#Region './Public/Remove-UserProfilesFromRegistry.ps1' -1

<#
.SYNOPSIS
Removes user profiles from the Windows registry based on SIDs, Usernames, or UserProfile objects.

.DESCRIPTION
The Remove-UserProfilesFromRegistry function allows you to remove user profiles from the Windows registry.
It supports three parameter sets: UserProfileSet, SIDSet, and UserNameSet. The function can be used in
audit-only mode, where no actual removal is performed, or in deletion mode where profiles are removed.

If AuditOnly is specified, the function will simply output the profiles to be removed without actually performing
any deletions. The function can prompt for confirmation before deletion if required, or use the Force switch
to bypass confirmation.

.PARAMETER UserProfiles
An array of UserProfile objects to remove from the registry. This parameter is mandatory in the "UserProfileSet"
parameter set. UserProfiles should include the necessary information such as SID, ProfilePath, and ComputerName.

.PARAMETER SIDs
An array of SIDs of user profiles to remove from the registry. This parameter is mandatory in the "SIDSet"
parameter set.

.PARAMETER Usernames
An array of usernames to resolve into SIDs and remove from the registry. This parameter is mandatory in the
"UserNameSet" parameter set.

.PARAMETER ComputerName
Specifies the computer name from which the user profiles should be removed. If not provided, it defaults to
the local computer.

.PARAMETER AuditOnly
When specified, the function only audits the user profiles and does not perform actual deletion. It will output
information about the profiles that would have been removed.

.PARAMETER Force
Forces the removal of the user profiles without prompting for confirmation.

.Outputs
ProfileDeletionResult objects that contain information about the deletion results.

.EXAMPLE
Remove-UserProfilesFromRegistry -SIDs "S-1-5-21-1234567890-1", "S-1-5-21-1234567890-2"

Removes user profiles associated with the provided SIDs from the registry of the local computer.

.EXAMPLE
Remove-UserProfilesFromRegistry -Usernames "john.doe", "jane.smith" -ComputerName "SERVER01" -Force

Removes the profiles associated with the specified usernames on the "SERVER01" machine without prompting for confirmation.

.EXAMPLE
Remove-UserProfilesFromRegistry -UserProfiles $userProfileList -AuditOnly

Audits the profiles in the $userProfileList and outputs what would have been removed without performing actual deletions.

.NOTES
Requires administrative privileges to remove profiles from the registry.

.LINK
Get-Help about_Registry
Get-Help about_Profiles
#>

function Remove-UserProfilesFromRegistry
{
    [outputType([ProfileDeletionResult])]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = "UserProfileSet")]
        [ValidateNotNullOrEmpty()]
        [UserProfile[]]$UserProfiles,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = "SIDSet")]
        [ValidateNotNullOrEmpty()]
        [string[]]$SIDs,


        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = "UserNameSet")]
        [ValidateNotNullOrEmpty()]
        [string[]]$Usernames,

        [string]$ComputerName = $env:COMPUTERNAME,
        [switch]$AuditOnly,
        [switch]$Force
        # Default confirm behavior to true
    )

    Begin
    {
        Try
        {
            # Retrieve necessary environment variables
            $RegistryPath = Test-EnvironmentVariable -Name 'WinProfileOps_RegistryPath'
            $ProfileFolderPath = Test-EnvironmentVariable -Name 'WinProfileOps_ProfileFolderPath'
            $RegistryHive = Test-EnvironmentVariable -Name 'WinProfileOps_RegistryHive'

            # Resolve SIDs if Usernames are provided
            if ($PSCmdlet.ParameterSetName -eq 'UserNameSet')
            {
                $SIDs = Resolve-UsernamesToSIDs -Usernames $Usernames -ComputerName $ComputerName

                # If no SIDs were resolved, stop execution by throwing a terminating error
                if (-not $SIDs)
                {
                    $errorRecord = New-Object System.Management.Automation.ErrorRecord (
                        [System.Exception]::new("No SIDs could be resolved for the provided usernames."),
                        "NoSIDsResolved",
                        [System.Management.Automation.ErrorCategory]::InvalidArgument,
                        $null
                    )
                    $PSCmdlet.ThrowTerminatingError($errorRecord)
                }
            }

            # Group UserProfiles by computer name if using UserProfileSet
            if ($PSCmdlet.ParameterSetName -eq 'UserProfileSet')
            {
                $profilesByComputer = $UserProfiles | Group-Object -Property ComputerName
            }

            # Handle confirmation: default behavior should be prompting unless explicitly set to false
            $Confirm = if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey('Confirm'))
            {
                $PSCmdlet.MyInvocation.BoundParameters['Confirm']
            }
            else
            {
                $true # Default to true, always prompt unless explicitly overridden
            }

        }
        Catch
        {
            Write-Error "Failed to initialize the function. Error: $_.Exception.Message"
            return
        }
    }
    Process
    {
        # Process UserProfileSet - prompt per computer
        if ($PSCmdlet.ParameterSetName -eq 'UserProfileSet')
        {
            foreach ($profileGroup in $profilesByComputer)
            {
                $thisComputerName = $profileGroup.Name
                $SIDs = $profileGroup.Group.GetEnumerator().SID
                $profileCount = $profileGroup.Count

                try
                {
                    # Call the confirmation prompt and skip this group if the user does not confirm
                    if (-not (PromptForConfirmation -ComputerName $thisComputerName -ItemCount $profileCount -AuditOnly:$AuditOnly -Context $PSCmdlet -confirm:$Confirm))
                    {
                        Write-Verbose "User chose not to continue for $thisComputerName, skipping."
                        continue
                    }

                    # Process the profiles for this computer
                    $SIDs | Invoke-UserProfileRegRemoval -ComputerName $thisComputerName `
                        -RegistryPath $RegistryPath -ProfileFolderPath $ProfileFolderPath `
                        -RegistryHive $RegistryHive -Force:$Force -AuditOnly:$AuditOnly -Confirm:$Confirm
                }
                catch
                {
                    # Handle any errors that occur during processing of this computer
                    Write-Error "Failed to process $thisComputerName. Error: $_.Exception.Message"
                    continue  # Move to the next computer in the loop
                }
            }
        }

        # Process SIDSet and UserNameSet - prompt once for the given computer name
        if ($PSCmdlet.ParameterSetName -eq 'SIDSet' -or $PSCmdlet.ParameterSetName -eq 'UserNameSet')
        {
            $itemCount = $SIDs.Count

            # Call the confirmation prompt and stop if the user does not confirm
            if (-not (PromptForConfirmation -ComputerName $ComputerName -ItemCount $itemCount -AuditOnly:$AuditOnly -Context $PSCmdlet -confirm:$Confirm))
            {
                Write-Verbose "User chose not to continue for $thisComputerName, skipping."
                return
            }

            # Process the SIDs for this computer name
            $SIDs | Invoke-UserProfileRegRemoval -ComputerName $ComputerName `
                -RegistryPath $RegistryPath -ProfileFolderPath $ProfileFolderPath `
                -RegistryHive $RegistryHive -Force:$Force -AuditOnly:$AuditOnly -Confirm:$Confirm
        }
    }

    End
    {
        # No need to manually return results; PowerShell will output naturally
    }
}
#EndRegion './Public/Remove-UserProfilesFromRegistry.ps1' 194