EnhancedAO.Graph.SignInLogs.psm1

#Region '.\Public\Add-Result.ps1' -1


# Function to add results to the context
function Add-Result {
    param (
        [Parameter(Mandatory = $true)]
        $Context,
        [Parameter(Mandatory = $true)]
        $Item,
        [Parameter(Mandatory = $true)]
        [string] $DeviceId,
        [Parameter(Mandatory = $true)]
        [string] $DeviceState,
        [Parameter(Mandatory = $true)]
        [bool] $HasPremiumLicense,
        [Parameter(Mandatory = $false)]
        [string]$OSVersion
    )

    try {
        $deviceName = $Item.DeviceDetail.DisplayName
        if ([string]::IsNullOrWhiteSpace($deviceName)) {
            $deviceName = "BYOD"
        }

        # Determine the compliance status
        $complianceStatus = if ($Item.DeviceDetail.IsCompliant) { "Compliant" } else { "Non-Compliant" }

        # Determine the user license
        $userLicense = if ($HasPremiumLicense) { "Microsoft 365 Business Premium" } else { "Other" }

        # Determine the sign-in status based on the error code
        $signInStatus = if ($Item.Status.ErrorCode -eq 0) { "Success" } else { "Failure" }

        # Create the result object directly
        $result = [PSCustomObject]@{
            DeviceName             = $deviceName
            UserName               = $Item.UserDisplayName
            DeviceEntraID          = $DeviceId
            UserEntraID            = $Item.UserId
            DeviceOS               = $Item.DeviceDetail.OperatingSystem
            OSVersion              = $osVersion
            DeviceComplianceStatus = $complianceStatus
            DeviceStateInIntune    = $DeviceState
            TrustType              = $Item.DeviceDetail.TrustType
            UserLicense            = $userLicense
            SignInStatus           = $signInStatus   # New property for Sign-In Status
            City                   = $Item.Location.City  # New property for City
            State                  = $Item.Location.State # New property for State
            CountryOrRegion        = $Item.Location.CountryOrRegion # New property for Country/Region
        }
        
        # Add the result to the context
        $Context.Results.Add($result)

        Write-EnhancedLog -Message "Successfully added result for device: $deviceName for user: $($Item.UserDisplayName) with status: $signInStatus" -Level "INFO"
    }
    catch {
        Handle-Error -ErrorRecord $_
        Write-EnhancedLog -Message "Failed to add result for device: $($Item.DeviceDetail.DisplayName)" -Level "ERROR"
    }
}

# # Example of how to use the functions
# $context = New-ProcessingContext
# $deviceDetail = New-DeviceDetail -DeviceId "device1" -DisplayName "Device One" -OperatingSystem "Windows 10" -IsCompliant $true -TrustType "AzureAD"
# $item = New-SignInLog -UserDisplayName "John Doe" -UserId "user1" -DeviceDetail $deviceDetail
# Add-Result -Context $context -Item $item -DeviceId "device1" -DeviceState "Active" -HasPremiumLicense $true
#EndRegion '.\Public\Add-Result.ps1' 68
#Region '.\Public\Check-DeviceStateInIntune.ps1' -1

function Initialize-HttpClient {
    param (
        [hashtable]$Headers
    )

    $httpClient = [System.Net.Http.HttpClient]::new()
    $httpClient.DefaultRequestHeaders.Add("Authorization", $Headers["Authorization"])
    return $httpClient
}

function Check-DeviceStateInIntune {
    param (
        [Parameter(Mandatory = $true)]
        [string]$EntraDeviceId,
        [Parameter(Mandatory = $true)]
        [string]$Username,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    if ([string]::IsNullOrWhiteSpace($EntraDeviceId)) {
        return "Absent"
    }

    Write-EnhancedLog -Message "Checking device state in Intune for Entra Device ID: $EntraDeviceId for username: $Username" -Level 'INFO'

    $graphApiUrl = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?`$filter=azureADDeviceId eq '$EntraDeviceId'"
    Write-EnhancedLog -Message "Constructed Graph API URL: $graphApiUrl" -Level 'INFO'

    $httpClient = Initialize-HttpClient -Headers $Headers

    try {
        $response = $httpClient.GetStringAsync($graphApiUrl).Result

        if (-not [string]::IsNullOrEmpty($response)) {
            $responseJson = [System.Text.Json.JsonDocument]::Parse($response)
            $valueProperty = $responseJson.RootElement.GetProperty("value")

            if ($valueProperty.GetArrayLength() -gt 0) {
                Write-EnhancedLog -Message "Device is present in Intune." -Level 'INFO'
                return "Present"
            } else {
                Write-EnhancedLog -Message "Device is absent in Intune." -Level 'WARNING'
                return "Absent"
            }
        } else {
            Write-EnhancedLog -Message "Received empty response from Intune API." -Level 'WARNING'
            return "NoData"
        }
    } catch {
        Handle-Error -ErrorRecord $_
        return "Error"
    } finally {
        if ($null -ne $responseJson) {
            $responseJson.Dispose()
        }
        $httpClient.Dispose()
    }
}

# # Example usage
# $entraDeviceId = "your_device_id"
# $username = "your_username"
# $headers = @{ "Authorization" = "Bearer your_token" }

# $deviceState = Check-DeviceStateInIntune -EntraDeviceId $entraDeviceId -Username $username -Headers $headers
# Write-Output "Device State: $deviceState"
#EndRegion '.\Public\Check-DeviceStateInIntune.ps1' 68
#Region '.\Public\Export-SignInLogs.ps1' -1

function Export-SignInLogs {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ScriptRoot,
        [Parameter(Mandatory = $true)]
        [string]$ExportsFolderName,
        [Parameter(Mandatory = $true)]
        [string]$ExportSubFolderName,
        [Parameter(Mandatory = $true)]
        [hashtable]$headers,
        [Parameter(Mandatory = $true)]
        [string]$url
    
    )

    # Ensure the exports folder is clean before exporting
    $exportFolder = Ensure-ExportsFolder -BasePath $ScriptRoot -ExportsFolderName $ExportsFolderName -ExportSubFolderName $ExportSubFolderName

    # Get the sign-in logs (assuming you have a way to fetch these logs)
    # $signInLogs = Get-SignInLogs # Replace with the actual command to get sign-in logs

    $signInLogs = Get-SignInLogs -url $url -Headers $headers

    # Check if there are no sign-in logs
    if ($signInLogs.Count -eq 0) {
        Write-EnhancedLog -Message "NO sign-in logs found." -Level "WARNING"
        return
    }

    # Generate a timestamp for the export
    $timestamp = Get-Date -Format "yyyyMMddHHmmss"
    $baseOutputPath = Join-Path -Path $exportFolder -ChildPath "SignInLogs_$timestamp"

    # Setup parameters for Export-Data using splatting
    $exportParams = @{
        Data             = $signInLogs
        BaseOutputPath   = $baseOutputPath
        # IncludeCSV = $true
        IncludeJSON      = $true
        # IncludeXML = $true
        # IncludePlainText = $true
        # IncludeExcel = $true
        # IncludeYAML = $true
    }

    # Call the Export-Data function with splatted parameters
    Export-Data @exportParams
    Write-EnhancedLog -Message "Data export completed successfully." -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
}


# # Define the root path where the scripts and exports are located
# $scriptRoot = "C:\MyScripts"

# # Optionally, specify the names for the exports folder and subfolder
# $exportsFolderName = "CustomExports"
# $exportSubFolderName = "CustomSignInLogs"

# # Call the function to export sign-in logs to XML (and other formats)
# Export-SignInLogsToXML -ScriptRoot $scriptRoot -ExportsFolderName $exportsFolderName -ExportSubFolderName $exportSubFolderName
#EndRegion '.\Public\Export-SignInLogs.ps1' 62
#Region '.\Public\ExportAndProcessSignInLogs.ps1' -1

function ExportAndProcessSignInLogs {
    param (
        [Parameter(Mandatory = $true)]
        [string]$ScriptRoot,
        [Parameter(Mandatory = $true)]
        [string]$ExportsFolderName,
        [Parameter(Mandatory = $true)]
        [string]$ExportSubFolderName,
        [Parameter(Mandatory = $true)]
        [string]$url,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    try {
        $ExportSignInLogsparams = @{
            ScriptRoot          = $ScriptRoot
            ExportsFolderName   = $ExportsFolderName
            ExportSubFolderName = $ExportSubFolderName
            url                 = $url
            Headers             = $Headers
        }

        # Ask user if they want to export fresh sign-in logs
        $exportFreshLogs = Read-Host "Would you like to export fresh logs? (yes/no)"

        if ($exportFreshLogs -eq 'yes') {
            Export-SignInLogs @ExportSignInLogsparams
        }

        $subFolderPath = Join-Path -Path $ScriptRoot -ChildPath $ExportsFolderName
        $subFolderPath = Join-Path -Path $subFolderPath -ChildPath $ExportSubFolderName

        Write-EnhancedLog -Message "Looking for JSON files in $subFolderPath" -Level "INFO"

        $latestJsonFile = Find-LatestJsonFile -Directory $subFolderPath

        if ($latestJsonFile) {
            Write-EnhancedLog -Message "Latest JSON file found: $latestJsonFile" -Level "INFO"
            $signInLogs = Load-SignInLogs -JsonFilePath $latestJsonFile

            # Log the count of sign-in logs found
            Write-EnhancedLog -Message "Loaded $(@($signInLogs).Count) sign-in logs from file: $latestJsonFile" -Level "INFO"
            
            # Debugger can be enabled here if needed for further inspection
            # Wait-Debugger
            
            if ($null -eq $signInLogs -or @($signInLogs).Count -eq 0) {
                Write-EnhancedLog -Message "No sign-in logs found in $latestJsonFile." -Level "WARNING"
                return @()
            }
            else {
                Write-EnhancedLog -Message "Sign-in logs found in $latestJsonFile. Starting to process them." -Level "NOTICE"
                # Process-AllDevices -Json $signInLogs -Headers $Headers
                return $signInLogs
            }
            

            # Wait-Debugger
        }
        else {
            Write-EnhancedLog -Message "No JSON file found to load sign-in logs." -Level "WARNING"
            return @()
        }
    }
    catch {
        Handle-Error -ErrorRecord $_
        return @()
    }
}
#EndRegion '.\Public\ExportAndProcessSignInLogs.ps1' 71
#Region '.\Public\Fetch-DeviceStateWithRetry.ps1' -1

function Fetch-DeviceStateWithRetry {
    param (
        [Parameter(Mandatory = $true)]
        [string]$DeviceId,
        [Parameter(Mandatory = $true)]
        [string]$Username,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    $retryCount = 0
    $maxRetries = 3
    $fetchSuccess = $false
    $deviceState = "Unknown"

    do {
        try {
            $deviceState = Check-DeviceStateInIntune -entraDeviceId $DeviceId -username $Username -Headers $Headers
            $fetchSuccess = $true
        } catch {
            Write-EnhancedLog -Message "Failed to check device state for device ID: $DeviceId. Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
            $retryCount++
            Start-Sleep -Seconds 2
        }
    } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

    if (-not $fetchSuccess) {
        Write-EnhancedLog -Message "Failed to check device state for device ID: $DeviceId after $maxRetries attempts." -Level "ERROR"
    }

    return $deviceState
}
#EndRegion '.\Public\Fetch-DeviceStateWithRetry.ps1' 33
#Region '.\Public\Fetch-OSVersion.ps1' -1

function Fetch-OSVersion {
    param (
        [Parameter(Mandatory = $true)]
        [string]$DeviceId,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    Begin {
        Write-EnhancedLog -Message "Starting Fetch-OSVersion function for Device ID: $DeviceId" -Level "INFO"
        Log-Params -Params @{ DeviceId = $DeviceId }
    }

    Process {
        $uri = "https://graph.microsoft.com/v1.0/devices?$filter=deviceId eq '$DeviceId'"
        $httpClient = Initialize-HttpClient -Headers $Headers

        try {
            Write-EnhancedLog -Message "Fetching OS version from URL: $uri" -Level "INFO"

            $response = $httpClient.GetStringAsync($uri).Result
            if (-not [string]::IsNullOrEmpty($response)) {
                $responseJson = [System.Text.Json.JsonDocument]::Parse($response)
                $deviceDetails = $responseJson.RootElement.GetProperty("value").EnumerateArray() | Where-Object { $_.GetProperty("deviceId").GetString() -eq $DeviceId }

                if ($deviceDetails) {
                    $osVersion = $deviceDetails.GetProperty("operatingSystemVersion").GetString()
                    Write-EnhancedLog -Message "OS Version for Device ID $DeviceId $osVersion" -Level "INFO"
                    $responseJson.Dispose()
                    return $osVersion
                } else {
                    Write-EnhancedLog -Message "No matching device found for Device ID $DeviceId" -Level "WARNING" -ForegroundColor Yellow
                    return $null
                }
            } else {
                Write-EnhancedLog -Message "Received empty response from OS version API for Device ID: $DeviceId." -Level "WARNING" -ForegroundColor Yellow
                return $null
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while fetching OS version: $($_.Exception.Message)" -Level "ERROR" -ForegroundColor Red
            Handle-Error -ErrorRecord $_
            return $null
        } finally {
            $httpClient.Dispose()
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Fetch-OSVersion function for Device ID: $DeviceId" -Level "INFO"
    }
}
#EndRegion '.\Public\Fetch-OSVersion.ps1' 52
#Region '.\Public\Fetch-OSVersionWithRetry.ps1' -1


function Fetch-OSVersionWithRetry {
    param (
        [Parameter(Mandatory = $true)]
        [string]$DeviceId,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    $retryCount = 0
    $maxRetries = 3
    $fetchSuccess = $false
    $osVersion = "Unknown"

    do {
        try {
            $osVersion = Fetch-OSVersion -DeviceId $DeviceId -Headers $Headers
            $fetchSuccess = $true
        } catch {
            Write-EnhancedLog -Message "Failed to fetch OS version for device ID: $DeviceId. Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
            $retryCount++
            Start-Sleep -Seconds 2
        }
    } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

    if (-not $fetchSuccess) {
        Write-EnhancedLog -Message "Failed to fetch OS version for device ID: $DeviceId after $maxRetries attempts." -Level "ERROR"
    }

    return $osVersion
}
#EndRegion '.\Public\Fetch-OSVersionWithRetry.ps1' 32
#Region '.\Public\Fetch-UserLicense.ps1' -1

function Fetch-UserLicense {
    param (
        [Parameter(Mandatory = $true)]
        [string]$UserId,
        [Parameter(Mandatory = $true)]
        [string]$Username,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    try {
        Write-EnhancedLog -Message "Fetching licenses for user: $Username with ID: $UserId" -ForegroundColor Cyan
        $userLicenses = Get-UserLicenses -userId $UserId -username $Item.userDisplayName  -Headers $Headers
        return $userLicenses
    } catch {
        Handle-Error -ErrorRecord $_
        # return $null
    }
}
#EndRegion '.\Public\Fetch-UserLicense.ps1' 20
#Region '.\Public\Fetch-UserLicensesWithRetry.ps1' -1


function Fetch-UserLicensesWithRetry {
    param (
        [Parameter(Mandatory = $true)]
        [string]$UserId,
        [Parameter(Mandatory = $true)]
        [string]$Username,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    $retryCount = 0
    $maxRetries = 3
    $fetchSuccess = $false
    $userLicenses = @()

    do {
        try {
            $userLicenses = Fetch-UserLicense -UserId $UserId -Username $Username -Headers $Headers
            $fetchSuccess = $true
        } catch {
            Write-EnhancedLog -Message "Failed to fetch licenses for user: $Username. Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
            $retryCount++
            Start-Sleep -Seconds 2
        }
    } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

    if (-not $fetchSuccess) {
        Write-EnhancedLog -Message "Failed to fetch licenses for user: $Username after $maxRetries attempts." -Level "ERROR"
    }

    return $userLicenses
}
#EndRegion '.\Public\Fetch-UserLicensesWithRetry.ps1' 34
#Region '.\Public\Find-LatestJsonFile.ps1' -1

# Function to find the latest JSON file in the specified directory
function Find-LatestJsonFile {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Directory
    )

    $jsonFiles = Get-ChildItem -Path $Directory -Filter *.json | Sort-Object LastWriteTime -Descending

    if ($jsonFiles.Count -gt 0) {
        return $jsonFiles[0].FullName
    } else {
        Write-EnhancedLog -Message "No JSON files found in $Directory." -Level "ERROR"
        # return $null
    }
}


#EndRegion '.\Public\Find-LatestJsonFile.ps1' 19
#Region '.\Public\Generate-LicenseReports.ps1' -1

# Function to generate reports based on user licenses
function Generate-LicenseReports {
    param (
        [Parameter(Mandatory = $true)]
        [System.Collections.Generic.List[PSCustomObject]]$Results,
        [Parameter(Mandatory = $true)]
        [string]$PSScriptRoot,
        [Parameter(Mandatory = $true)]
        [string]$ExportsFolderName
    )

    # Remove duplicates based on UserEntraID
    $uniqueResults = $Results | Sort-Object -Property UserEntraID -Unique

    # Generate reports for users with and without Business Premium licenses
    $premiumLicenses = $uniqueResults | Where-Object { $_.UserLicense -eq 'Microsoft 365 Business Premium' }
    $nonPremiumLicenses = $uniqueResults | Where-Object { $_.UserLicense -ne 'Microsoft 365 Business Premium' }

    $premiumLicenses | Export-Csv "$PSScriptRoot/$ExportsFolderName/Report_PremiumLicenses.csv" -NoTypeInformation
    $nonPremiumLicenses | Export-Csv "$PSScriptRoot/$ExportsFolderName/Report_NonPremiumLicenses.csv" -NoTypeInformation

    # Output totals to console
    Write-EnhancedLog -Message "Total users with Business Premium licenses: $($premiumLicenses.Count)" -Level "INFO"
    Write-EnhancedLog -Message "Total users without Business Premium licenses: $($nonPremiumLicenses.Count)" -Level "INFO"

    Write-EnhancedLog -Message "Generated reports for users with and without Business Premium licenses." -Level "INFO"
}

# # Example usage
# $Json = @() # Your JSON data here
# $Headers = @{} # Your actual headers
# $PSScriptRoot = "C:\Path\To\ScriptRoot" # Update to your script root path
# $ExportsFolderName = "CustomExports"

# $results = Process-AllDevices -Json $Json -Headers $Headers

# # Generate and export the reports
# Generate-LicenseReports -Results $results -PSScriptRoot $PSScriptRoot -ExportsFolderName $ExportsFolderName
#EndRegion '.\Public\Generate-LicenseReports.ps1' 39
#Region '.\Public\Generate-PII-RemovedReport.ps1' -1

# Function to generate a report for PII Removed cases
function Generate-PII-RemovedReport {
    param (
        [Parameter(Mandatory = $true)]
        [System.Collections.Generic.List[PSCustomObject]]$Results,
        [Parameter(Mandatory = $true)]
        [string]$PSScriptRoot,
        [Parameter(Mandatory = $true)]
        [string]$ExportsFolderName
    )

    # Filter results for PII Removed (external) cases
    $piiRemovedResults = $Results | Where-Object { $_.DeviceStateInIntune -eq 'External' }

    # Export the results to a CSV file
    $piiRemovedResults | Export-Csv "$PSScriptRoot/$ExportsFolderName/Report_PIIRemoved.csv" -NoTypeInformation

    # Output totals to console
    Write-EnhancedLog -Message "Total users with PII Removed (external Azure AD/Entra ID tenants): $($piiRemovedResults.Count)" -Level "Warning"
    Write-EnhancedLog -Message "Generated report for users with PII Removed (external Azure AD/Entra ID tenants." -Level "INFO"
}

# # Example usage
# $Json = @() # Your JSON data here
# $Headers = @{} # Your actual headers
# $PSScriptRoot = "C:\Path\To\ScriptRoot" # Update to your script root path
# $ExportsFolderName = "CustomExports"

# $results = Process-AllDevices -Json $Json -Headers $Headers

# # Generate and export the PII Removed report
# Generate-PII-RemovedReport -Results $results -PSScriptRoot $PSScriptRoot -ExportsFolderName $ExportsFolderName
#EndRegion '.\Public\Generate-PII-RemovedReport.ps1' 33
#Region '.\Public\Get-UserLicenses.ps1' -1

function Initialize-HttpClient {
    param (
        [hashtable]$Headers
    )

    $httpClient = [System.Net.Http.HttpClient]::new()
    $httpClient.DefaultRequestHeaders.Add("Authorization", $Headers["Authorization"])
    return $httpClient
}


function Get-UserLicenses {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$UserId,
        [Parameter(Mandatory = $true)]
        [string]$Username,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    Begin {
        Write-EnhancedLog -Message "Starting Get-UserLicenses function" -Level "INFO"
        Log-Params -Params @{ UserId = $UserId; Username = $Username }
    }

    Process {
        $licenses = [System.Collections.Generic.List[string]]::new()
        $uri = "https://graph.microsoft.com/v1.0/users/$UserId/licenseDetails"
        $httpClient = Initialize-HttpClient -Headers $Headers

        try {
            Write-EnhancedLog -Message "Fetching licenses for user ID: $UserId with username: $Username" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)

            $response = $httpClient.GetStringAsync($uri).Result
            if (-not [string]::IsNullOrEmpty($response)) {
                $responseJson = [System.Text.Json.JsonDocument]::Parse($response)
                $valueProperty = $responseJson.RootElement.GetProperty("value")
                foreach ($license in $valueProperty.EnumerateArray()) {
                    $skuId = $license.GetProperty("skuId").GetString()
                    $licenses.Add($skuId)
                    Write-EnhancedLog -Message "Found license for user: $Username with SKU ID: $skuId" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
                }
                $responseJson.Dispose()
            } else {
                Write-EnhancedLog -Message "Received empty response from license API." -Level "WARNING" -ForegroundColor ([ConsoleColor]::Yellow)
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while fetching licenses: $($_.Exception.Message)" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
            Handle-Error -ErrorRecord $_
            throw
        } finally {
            $httpClient.Dispose()
        }

        return $licenses
    }

    End {
        Write-EnhancedLog -Message "Exiting Get-UserLicenses function" -Level "INFO"
    }
}





# # Example usage
# $userId = "your_user_id"
# $username = "your_username"
# $headers = @{ "Authorization" = "Bearer your_token" }

# $licenses = Get-UserLicenses -UserId $userId -Username $username -Headers $headers
# Write-Output "Licenses: $($licenses -join ', ')"



#EndRegion '.\Public\Get-UserLicenses.ps1' 79
#Region '.\Public\Handle-ExternalAADTenant.ps1' -1

function Handle-ExternalAADTenant {
    param (
        [Parameter(Mandatory = $true)]
        [PSCustomObject]$Item,
        [Parameter(Mandatory = $true)]
        [PSCustomObject]$Context,
        [string]$UniqueId
    )

    if ([string]::Equals($Item.deviceDetail.deviceId, "{PII Removed}", [System.StringComparison]::OrdinalIgnoreCase)) {
        if ($Context.UniqueDeviceIds.Add($UniqueId)) {
            Write-EnhancedLog -Message "External Azure AD tenant detected for user: $($Item.userDisplayName)" -Level "INFO"
            Add-Result -Context $Context -Item $Item -DeviceId "N/A" -DeviceState "External" -HasPremiumLicense $false -OSVersion $null
        }
        return $true
    }
    return $false
}
#EndRegion '.\Public\Handle-ExternalAADTenant.ps1' 19
#Region '.\Public\Initialize-Context.ps1' -1

function Initialize-Context {
    param (
        [Parameter(Mandatory = $true)]
        [PSCustomObject]$Context
    )

    if (-not $Context.UniqueDeviceIds) {
        $Context.UniqueDeviceIds = [System.Collections.Generic.HashSet[string]]::new()
    }
}
#EndRegion '.\Public\Initialize-Context.ps1' 11
#Region '.\Public\Load-SignInLogs.ps1' -1

# function Load-SignInLogs {
# param (
# [Parameter(Mandatory = $true)]
# [string]$JsonFilePath
# )

# $signInLogs = [System.Collections.Generic.List[PSCustomObject]]::new()
# Write-EnhancedLog -Message "Opening file: $JsonFilePath" -Level 'INFO'
# $fileStream = [System.IO.FileStream]::new($JsonFilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read, 4096, [System.IO.FileOptions]::SequentialScan)

# try {
# $jsonDoc = [System.Text.Json.JsonDocument]::Parse($fileStream)

# foreach ($element in $jsonDoc.RootElement.EnumerateArray()) {
# $deviceDetail = [PSCustomObject]@{
# DeviceId = $element.GetProperty("deviceDetail").GetProperty("deviceId").GetString()
# DisplayName = $element.GetProperty("deviceDetail").GetProperty("displayName").GetString()
# OperatingSystem = $element.GetProperty("deviceDetail").GetProperty("operatingSystem").GetString()
# IsCompliant = $element.GetProperty("deviceDetail").GetProperty("isCompliant").GetBoolean()
# TrustType = $element.GetProperty("deviceDetail").GetProperty("trustType").GetString()
# }

# $status = [PSCustomObject]@{
# ErrorCode = $element.GetProperty("status").GetProperty("errorCode").GetInt32()
# FailureReason = $element.GetProperty("status").GetProperty("failureReason").GetString()
# AdditionalDetails = $element.GetProperty("status").GetProperty("additionalDetails").GetString() #returns a get string error possibly due to some being null and some having special characters like like continuation
# }

# $location = [PSCustomObject]@{
# City = $element.GetProperty("location").GetProperty("city").GetString()
# State = $element.GetProperty("location").GetProperty("state").GetString()
# CountryOrRegion = $element.GetProperty("location").GetProperty("countryOrRegion").GetString()
# }

# $signInLog = [PSCustomObject]@{
# UserDisplayName = $element.GetProperty("userDisplayName").GetString()
# UserId = $element.GetProperty("userId").GetString()
# DeviceDetail = $deviceDetail
# Status = $status # Include the status object in the sign-in log
# Location = $location # Include the location object in the sign-in log
# }

# $signInLogs.Add($signInLog)
# }

# Write-EnhancedLog -Message "Sign-in logs loaded successfully from $JsonFilePath." -Level "INFO"
# } catch {
# Handle-Error -ErrorRecord $_
# } finally {
# $fileStream.Dispose()
# }

# return $signInLogs
# }


function Load-SignInLogs {
    param (
        [string]$JsonFilePath
    )

    $signInLogs = [System.Collections.Generic.List[PSCustomObject]]::new()

    try {
        $jsonContent = Get-Content -Path $JsonFilePath -Raw | ConvertFrom-Json

        foreach ($entry in $jsonContent) {
            $signInLogs.Add([PSCustomObject]@{
                UserDisplayName = $entry.userDisplayName
                UserId          = $entry.userId
                DeviceDetail    = $entry.deviceDetail
                Status          = $entry.status
                Location        = $entry.location
            })
        }

        Write-Host "Sign-in logs loaded successfully from $JsonFilePath."
    } catch {
        Write-Host "An error occurred: $_"
    }

    return $signInLogs.ToArray()
}
#EndRegion '.\Public\Load-SignInLogs.ps1' 84
#Region '.\Public\New-DeviceDetail.ps1' -1

# Function to create a DeviceDetail object
function New-DeviceDetail {
    param (
        [string] $DeviceId,
        [string] $DisplayName,
        [string] $OperatingSystem,
        [bool] $IsCompliant,
        [string] $TrustType
    )
    [PSCustomObject]@{
        DeviceId        = $DeviceId
        DisplayName     = $DisplayName
        OperatingSystem = $OperatingSystem
        IsCompliant     = $IsCompliant
        TrustType       = $TrustType
    }
}
#EndRegion '.\Public\New-DeviceDetail.ps1' 18
#Region '.\Public\New-DeviceItem.ps1' -1

# Function to create a DeviceItem object
function New-DeviceItem {
    param (
        [string] $DeviceId,
        [string] $UserId,
        [string] $UserDisplayName
    )
    [PSCustomObject]@{
        DeviceId = $DeviceId
        UserId = $UserId
        UserDisplayName = $UserDisplayName
    }
}
#EndRegion '.\Public\New-DeviceItem.ps1' 14
#Region '.\Public\New-ProcessingContext.ps1' -1

# Function to create a ProcessingContext object

function New-ProcessingContext {
    [PSCustomObject]@{
        UniqueDeviceIds = [System.Collections.Generic.HashSet[string]]::new()
        Results = [System.Collections.Generic.List[PSCustomObject]]::new()
        # ProcessedUserDeviceIds = [System.Collections.Generic.Dictionary[string, System.Collections.Generic.HashSet[string]]]::new()
    }
}

#EndRegion '.\Public\New-ProcessingContext.ps1' 11
#Region '.\Public\New-Result.ps1' -1

# # Function to create a Result object
# function New-Result {
# param (
# [string] $DeviceName,
# [string] $UserName,
# [string] $DeviceEntraID,
# [string] $UserEntraID,
# [string] $DeviceOS,
# [string] $DeviceComplianceStatus,
# [string] $DeviceStateInIntune,
# [string] $TrustType,
# [string] $UserLicense,
# [string] $OSVersion
# )
# [PSCustomObject]@{
# DeviceName = $DeviceName
# UserName = $UserName
# DeviceEntraID = $DeviceEntraID
# UserEntraID = $UserEntraID
# DeviceOS = $DeviceOS
# DeviceComplianceStatus = $DeviceComplianceStatus
# DeviceStateInIntune = $DeviceStateInIntune
# TrustType = $TrustType
# UserLicense = $UserLicense
# OSVersion = $OSVersion
# }
# }
#EndRegion '.\Public\New-Result.ps1' 28
#Region '.\Public\New-SignInLog.ps1' -1

# Function to create a SignInLog object
function New-SignInLog {
    param (
        [string] $UserDisplayName,
        [string] $UserId,
        $DeviceDetail
    )
    [PSCustomObject]@{
        UserDisplayName = $UserDisplayName
        UserId = $UserId
        DeviceDetail = $DeviceDetail
    }
}
#EndRegion '.\Public\New-SignInLog.ps1' 14
#Region '.\Public\Process-DeviceItem-old.ps1' -1

# function Process-DeviceItem {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true)]
# $Item,
# [Parameter(Mandatory = $true)]
# $Context,
# [Parameter(Mandatory = $true)]
# $Headers
# )

# Begin {
# Write-EnhancedLog -Message "Starting Process-DeviceItem function" -Level "INFO"
# Log-Params -Params @{ Item = $Item; Context = $Context }
# if (-not $Context.UniqueDeviceIds) {
# $Context.UniqueDeviceIds = [System.Collections.Generic.HashSet[string]]::new()
# }
# }

# Process {
# # Ensure deviceDetail object and properties exist
# if (-not $Item.deviceDetail) {
# Write-EnhancedLog -Message "Missing deviceDetail for user: $($Item.userDisplayName)" -Level "WARNING"
# return
# }

# $deviceId = $Item.deviceDetail.deviceId
# $userId = $Item.userId
# $os = $Item.deviceDetail.operatingSystem

# if (-not $userId) {
# Write-EnhancedLog -Message "Missing userId for device item" -Level "WARNING"
# return
# }

# # Construct uniqueId based on availability of deviceId and OS for BYOD
# $uniqueId = if ([string]::IsNullOrWhiteSpace($deviceId)) {
# "$userId-$os".ToLowerInvariant()
# } else {
# $deviceId.ToLowerInvariant()
# }

# try {
# # Log the device and user information
# Write-EnhancedLog -Message "Processing device item for user: $($Item.userDisplayName) with unique ID: $uniqueId" -Level "INFO"

# # Handle external Azure AD tenant case
# if ([string]::Equals($deviceId, "{PII Removed}", [System.StringComparison]::OrdinalIgnoreCase)) {
# if ($Context.UniqueDeviceIds.Add($uniqueId)) {
# Write-EnhancedLog -Message "External Azure AD tenant detected for user: $($Item.userDisplayName)" -Level "INFO"
# Add-Result -Context $Context -Item $Item -DeviceId "N/A" -DeviceState "External" -HasPremiumLicense $false -OSVersion $null
# }
# return
# }

# # Process only if the unique ID is not already processed
# if ($Context.UniqueDeviceIds.Add($uniqueId)) {
# # Handle BYOD case
# if ([string]::IsNullOrWhiteSpace($deviceId)) {
# # Check if there are already devices with the same userId and OS
# # $existingBYODs = $Context.UniqueDeviceIds | Where-Object { $_ -like "$userId-*" }

# # if ($existingBYODs.Count -gt 0) {
# # Write-EnhancedLog -Message "User $($Item.userDisplayName) has multiple BYOD devices with the same OS: $os" -Level "WARNING"
# # }

# # Fetch user licenses with retry logic
# $retryCount = 0
# $maxRetries = 3
# do {
# try {
# $userLicenses = Fetch-UserLicense -UserId $userId -Username $Item.userDisplayName -Headers $Headers
# $fetchSuccess = $true
# } catch {
# Write-EnhancedLog -Message "Failed to fetch licenses for user: $($Item.userDisplayName). Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
# $fetchSuccess = $false
# $retryCount++
# Start-Sleep -Seconds 2
# }
# } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

# if (-not $fetchSuccess) {
# Write-EnhancedLog -Message "Failed to fetch licenses for user: $($Item.userDisplayName) after $maxRetries attempts." -Level "ERROR"
# $userLicenses = @()
# }

# $hasPremiumLicense = $userLicenses -and $userLicenses.Count -gt 0 -and $userLicenses.Contains("cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46")
# Write-EnhancedLog -Message "User $($Item.userDisplayName) has the following licenses: $($userLicenses -join ', ')" -Level "INFO"

# Add-Result -Context $Context -Item $Item -DeviceId "N/A" -DeviceState "BYOD" -HasPremiumLicense $hasPremiumLicense -OSVersion $null
# return
# }

# # Handle managed device case with retry logic
# $retryCount = 0
# $maxRetries = 3
# $deviceState = "Unknown"
# do {
# try {
# # Call the method to check device state
# $deviceState = Check-DeviceStateInIntune -entraDeviceId $deviceId -username $Item.userDisplayName -Headers $Headers
# $fetchSuccess = $true
# } catch {
# Write-EnhancedLog -Message "Failed to check device state for device ID: $deviceId. Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
# $fetchSuccess = $false
# $retryCount++
# Start-Sleep -Seconds 2
# }
# } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

# if (-not $fetchSuccess) {
# Write-EnhancedLog -Message "Failed to check device state for device ID: $deviceId after $maxRetries attempts." -Level "ERROR"
# }

# $retryCount = 0
# $osVersion = "Unknown"
# do {
# try {
# # Fetch OS version
# $osVersion = Fetch-OSVersion -DeviceId $deviceId -Headers $Headers
# $fetchSuccess = $true
# } catch {
# Write-EnhancedLog -Message "Failed to fetch OS version for device ID: $deviceId. Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
# $fetchSuccess = $false
# $retryCount++
# Start-Sleep -Seconds 2
# }
# } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

# if (-not $fetchSuccess) {
# Write-EnhancedLog -Message "Failed to fetch OS version for device ID: $deviceId after $maxRetries attempts." -Level "ERROR"
# }

# $retryCount = 0
# $userLicenses = @()
# do {
# try {
# # Fetch user licenses
# $userLicenses = Fetch-UserLicense -UserId $userId -Username $Item.userDisplayName -Headers $Headers
# $fetchSuccess = $true
# } catch {
# Write-EnhancedLog -Message "Failed to fetch licenses for user: $($Item.userDisplayName). Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
# $fetchSuccess = $false
# $retryCount++
# Start-Sleep -Seconds 2
# }
# } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

# if (-not $fetchSuccess) {
# Write-EnhancedLog -Message "Failed to fetch licenses for user: $($Item.userDisplayName) after $maxRetries attempts." -Level "ERROR"
# }

# $hasPremiumLicense = $userLicenses -and $userLicenses.Count -gt 0 -and $userLicenses.Contains("cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46")
# Write-EnhancedLog -Message "User $($Item.userDisplayName) has the following licenses: $($userLicenses -join ', ')" -Level "INFO"

# Add-Result -Context $Context -Item $Item -DeviceId $deviceId -DeviceState $deviceState -HasPremiumLicense $hasPremiumLicense -OSVersion $osVersion
# }
# } catch {
# Write-EnhancedLog -Message "An error occurred while processing the device item for user: $($Item.userDisplayName) - $_" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# }
# }

# End {
# Write-EnhancedLog -Message "Exiting Process-DeviceItem function" -Level "INFO"
# }
# }
#EndRegion '.\Public\Process-DeviceItem-old.ps1' 168
#Region '.\Public\Process-DeviceItem.ps1' -1

function Process-DeviceItem {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        $Item,
        [Parameter(Mandatory = $true)]
        $Context,
        [Parameter(Mandatory = $true)]
        $Headers
    )

    Begin {
        Write-EnhancedLog -Message "Starting Process-DeviceItem function" -Level "INFO"
        Log-Params -Params @{ Item = $Item; Context = $Context }
        Initialize-Context -Context $Context
    }

    Process {
        # Ensure ErrorCode is properly accessed
        $errorCode = $Item.Status.ErrorCode
        $failureReason = $Item.Status.FailureReason

        # Wait-Debugger

        if ($null -eq $errorCode) {
            Write-EnhancedLog -Message "Missing ErrorCode in the sign-in log for user $($Item.userDisplayName). Skipping this item." -Level "WARNING"
            return
        }

        # Check if sign-in was successful
        if ($errorCode -ne 0) {
            Write-EnhancedLog -Message "Sign-in attempt failed for user $($Item.userDisplayName) with ErrorCode: $errorCode - $failureReason" -Level "WARNING"
            return
        }

        # Ensure deviceDetail object and properties exist
        if (-not $Item.deviceDetail) {
            Write-EnhancedLog -Message "Missing deviceDetail for user: $($Item.userDisplayName)" -Level "WARNING"
            return
        }

        $deviceId = $Item.deviceDetail.deviceId
        $userId = $Item.userId
        $os = $Item.deviceDetail.operatingSystem

        if (-not $userId) {
            Write-EnhancedLog -Message "Missing userId for device item" -Level "WARNING"
            return
        }

        try {
            # Construct uniqueId based on availability of deviceId and OS for BYOD
            if ([string]::IsNullOrWhiteSpace($deviceId)) {
                $uniqueId = "$userId-$os".ToLowerInvariant()
            }
            else {
                $uniqueId = $deviceId.ToLowerInvariant()
            }

            # Log the device and user information
            Write-EnhancedLog -Message "Processing device item for user: $($Item.userDisplayName) with unique ID: $uniqueId" -Level "INFO"

            # Handle external Azure AD tenant case
            if (Handle-ExternalAADTenant -Item $Item -Context $Context -UniqueId $uniqueId) {
                return
            }

            # Process only if the unique ID is not already processed
            if ($Context.UniqueDeviceIds.Add($uniqueId)) {
                # Handle BYOD case
                if ([string]::IsNullOrWhiteSpace($deviceId)) {
                    # Fetch user licenses with retry logic
                    $userLicenses = Fetch-UserLicensesWithRetry -UserId $userId -Username $Item.userDisplayName -Headers $Headers
                    $hasPremiumLicense = $userLicenses -and $userLicenses.Count -gt 0 -and $userLicenses.Contains("cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46")
                    Write-EnhancedLog -Message "User $($Item.userDisplayName) has the following licenses: $($userLicenses -join ', ')" -Level "INFO"

                    Add-Result -Context $Context -Item $Item -DeviceId "N/A" -DeviceState "BYOD" -HasPremiumLicense $hasPremiumLicense -OSVersion $null
                    return
                }

                # Handle managed device case with retry logic
                $deviceState = Fetch-DeviceStateWithRetry -DeviceId $deviceId -Username $Item.userDisplayName -Headers $Headers
                $osVersion = Fetch-OSVersionWithRetry -DeviceId $deviceId -Headers $Headers

                $userLicenses = Fetch-UserLicensesWithRetry -UserId $userId -Username $Item.userDisplayName -Headers $Headers
                $hasPremiumLicense = $userLicenses -and $userLicenses.Count -gt 0 -and $userLicenses.Contains("cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46")
                Write-EnhancedLog -Message "User $($Item.userDisplayName) has the following licenses: $($userLicenses -join ', ')" -Level "INFO"

                Add-Result -Context $Context -Item $Item -DeviceId $deviceId -DeviceState $deviceState -HasPremiumLicense $hasPremiumLicense -OSVersion $osVersion
            }
            else {
                Write-EnhancedLog -Message "Device ID $uniqueId for user $($Item.userDisplayName) has already been processed and will be skipped." -Level "WARNING"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while processing the device item for user: $($Item.userDisplayName) - $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Process-DeviceItem function" -Level "INFO"
    }
}


#v2 with parallel processing

# function Process-DeviceItem {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true)]
# $Item,
# [Parameter(Mandatory = $true)]
# $Headers
# )

 

# Begin {
# Write-EnhancedLog -Message "Starting Process-DeviceItem function" -Level "INFO"
# Log-Params -Params @{ Item = $Item }

# # Local context-like structure
# $result = $null
# }

# Process {
# $errorCode = $Item.Status.ErrorCode
# $failureReason = $Item.Status.FailureReason

# if ($null -eq $errorCode) {
# Write-EnhancedLog -Message "Missing ErrorCode in the sign-in log for user $($Item.userDisplayName). Skipping this item." -Level "WARNING"
# return
# }

# if ($errorCode -ne 0) {
# Write-EnhancedLog -Message "Sign-in attempt failed for user $($Item.userDisplayName) with ErrorCode: $errorCode - $failureReason" -Level "WARNING"
# return
# }

# $deviceId = $Item.deviceDetail.deviceId
# $userId = $Item.userId
# $os = $Item.deviceDetail.operatingSystem

# if (-not $userId) {
# Write-EnhancedLog -Message "Missing userId for device item" -Level "WARNING"
# return
# }

# try {
# if ([string]::IsNullOrWhiteSpace($deviceId)) {
# $uniqueId = "$userId-$os".ToLowerInvariant()
# }
# else {
# $uniqueId = $deviceId.ToLowerInvariant()
# }

# Write-EnhancedLog -Message "Processing device item for user: $($Item.userDisplayName) with unique ID: $uniqueId" -Level "INFO"

# # Fetch user licenses with retry logic
# $userLicenses = Fetch-UserLicensesWithRetry -UserId $userId -Username $Item.userDisplayName -Headers $Headers
# $hasPremiumLicense = $userLicenses -and $userLicenses.Count -gt 0 -and $userLicenses.Contains("cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46")
# Write-EnhancedLog -Message "User $($Item.userDisplayName) has the following licenses: $($userLicenses -join ', ')" -Level "INFO"

# $deviceState = Fetch-DeviceStateWithRetry -DeviceId $deviceId -Username $Item.userDisplayName -Headers $Headers
# $osVersion = Fetch-OSVersionWithRetry -DeviceId $deviceId -Headers $Headers

# # Create a local result object
# $result = [PSCustomObject]@{
# DeviceName = $Item.DeviceDetail.DisplayName
# UserName = $Item.UserDisplayName
# DeviceEntraID = $deviceId
# UserEntraID = $userId
# DeviceOS = $Item.DeviceDetail.OperatingSystem
# OSVersion = $osVersion
# DeviceComplianceStatus = if ($Item.DeviceDetail.IsCompliant) { "Compliant" } else { "Non-Compliant" }
# DeviceStateInIntune = $deviceState
# TrustType = $Item.DeviceDetail.TrustType
# UserLicense = if ($hasPremiumLicense) { "Microsoft 365 Business Premium" } else { "Other" }
# SignInStatus = "Success"
# }

# Write-EnhancedLog -Message "Successfully processed device item for user: $($Item.userDisplayName)" -Level "INFO"
# }
# catch {
# Write-EnhancedLog -Message "An error occurred while processing the device item for user: $($Item.userDisplayName) - $_" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# }
# }

# End {
# Write-EnhancedLog -Message "Exiting Process-DeviceItem function" -Level "INFO"
# return $result
# }
# }




# # Global log queue
# $global:LogQueue = [System.Collections.Concurrent.ConcurrentQueue[System.String]]::new()

# # Start background job for async logging
# $global:LogJob = Start-Job -ScriptBlock {
# param ($logQueue)

# while ($true) {
# if ($logQueue.TryDequeue([ref]$logMessage)) {
# # Example of writing to a file, you can modify this to fit your logging method
# Add-Content -Path "logfile.txt" -Value $logMessage
# } else {
# Start-Sleep -Milliseconds 100
# }
# }
# } -ArgumentList $global:LogQueue




# function Write-AsyncLog {
# param (
# [string]$Message,
# [string]$Level = "INFO",
# [switch]$WriteToConsole = $true # Add a parameter to control console output
# )

# $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
# $logMessage = "[$timestamp][$Level] $Message"

# # Enqueue the log message for asynchronous file logging
# $global:LogQueue.Enqueue($logMessage)

# # Optionally write the log message to the console
# if ($WriteToConsole) {
# Write-Host $logMessage
# }
# }




# function Process-DeviceItem {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true)]
# $Item,
# [Parameter(Mandatory = $true)]
# $Context,
# [Parameter(Mandatory = $true)]
# $Headers
# )

# Begin {
# Write-AsyncLog -Message "Starting Process-DeviceItem function" -Level "INFO"
# Log-Params -Params @{ Item = $Item; Context = $Context }
# Initialize-Context -Context $Context
# }

# Process {
# $errorCode = $Item.Status.ErrorCode
# $failureReason = $Item.Status.FailureReason

# if ($null -eq $errorCode) {
# Write-AsyncLog -Message "Missing ErrorCode in the sign-in log for user $($Item.userDisplayName). Skipping this item." -Level "WARNING"
# return
# }

# if ($errorCode -ne 0) {
# Write-AsyncLog -Message "Sign-in attempt failed for user $($Item.userDisplayName) with ErrorCode: $errorCode - $failureReason" -Level "WARNING"
# return
# }

# if (-not $Item.deviceDetail) {
# Write-AsyncLog -Message "Missing deviceDetail for user: $($Item.userDisplayName)" -Level "WARNING"
# return
# }

# $deviceId = $Item.deviceDetail.deviceId
# $userId = $Item.userId
# $os = $Item.deviceDetail.operatingSystem

# if (-not $userId) {
# Write-AsyncLog -Message "Missing userId for device item" -Level "WARNING"
# return
# }

# try {
# $uniqueId = if ([string]::IsNullOrWhiteSpace($deviceId)) {
# "$userId-$os".ToLowerInvariant()
# } else {
# $deviceId.ToLowerInvariant()
# }

# Write-AsyncLog -Message "Processing device item for user: $($Item.userDisplayName) with unique ID: $uniqueId" -Level "INFO"

# if (Handle-ExternalAADTenant -Item $Item -Context $Context -UniqueId $uniqueId) {
# return
# }

# if ($Context.UniqueDeviceIds.Add($uniqueId)) {
# if ([string]::IsNullOrWhiteSpace($deviceId)) {
# $userLicenses = Fetch-UserLicensesWithRetry -UserId $userId -Username $Item.userDisplayName -Headers $Headers
# $hasPremiumLicense = $userLicenses -and $userLicenses.Count -gt 0 -and $userLicenses.Contains("cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46")
# Write-AsyncLog -Message "User $($Item.userDisplayName) has the following licenses: $($userLicenses -join ', ')" -Level "INFO"

# Add-Result -Context $Context -Item $Item -DeviceId "N/A" -DeviceState "BYOD" -HasPremiumLicense $hasPremiumLicense -OSVersion $null
# return
# }

# $deviceState = Fetch-DeviceStateWithRetry -DeviceId $deviceId -Username $Item.userDisplayName -Headers $Headers
# $osVersion = Fetch-OSVersionWithRetry -DeviceId $deviceId -Headers $Headers

# $userLicenses = Fetch-UserLicensesWithRetry -UserId $userId -Username $Item.userDisplayName -Headers $Headers
# $hasPremiumLicense = $userLicenses -and $userLicenses.Count -gt 0 -and $userLicenses.Contains("cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46")
# Write-AsyncLog -Message "User $($Item.userDisplayName) has the following licenses: $($userLicenses -join ', ')" -Level "INFO"

# Add-Result -Context $Context -Item $Item -DeviceId $deviceId -DeviceState $deviceState -HasPremiumLicense $hasPremiumLicense -OSVersion $osVersion
# }
# else {
# Write-AsyncLog -Message "Device ID $uniqueId for user $($Item.userDisplayName) has already been processed and will be skipped." -Level "WARNING"
# }
# }
# catch {
# Write-AsyncLog -Message "An error occurred while processing the device item for user: $($Item.userDisplayName) - $_" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# }
# }

# End {
# Write-AsyncLog -Message "Exiting Process-DeviceItem function" -Level "INFO"
# }
# }

#EndRegion '.\Public\Process-DeviceItem.ps1' 335
#Region '.\Public\Process-SignInLogs.ps1' -1

function Process-SignInLogs {
    param (
        [Parameter(Mandatory = $true)]
        [System.Collections.Generic.List[PSCustomObject]]$signInLogs,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    # Ensure the signInLogs variable is not null before using it
    if ($null -eq $signInLogs -or @($signInLogs).Count -eq 0) {
        Write-Warning "No sign-in logs were loaded."
        exit 1
    }
    else {
        Write-Host "Loaded $(@($signInLogs).Count) sign-in logs."
    }

    # Debugging: Print the first sign-in log entry
    if ($signInLogs.Count -gt 0) {
        $firstSignInLog = $signInLogs[0]
        
        # Determine the sign-in status based on the ErrorCode
        $signInStatus = if ($firstSignInLog.Status.ErrorCode -eq 0) { "Success" } else { "Failure" }
    
        Write-Host "First sign-in log entry:"
        Write-Host "UserDisplayName: $($firstSignInLog.UserDisplayName)"
        Write-Host "UserId: $($firstSignInLog.UserId)"
        Write-Host "SignInStatus: $signInStatus"  # Print the sign-in status
        Write-Host "DeviceDetail:"
        Write-Host " DeviceId: $($firstSignInLog.DeviceDetail.DeviceId)"
        Write-Host " DisplayName: $($firstSignInLog.DeviceDetail.DisplayName)"
        Write-Host " OperatingSystem: $($firstSignInLog.DeviceDetail.OperatingSystem)"
        Write-Host " IsCompliant: $($firstSignInLog.DeviceDetail.IsCompliant)"
        Write-Host " TrustType: $($firstSignInLog.DeviceDetail.TrustType)"
    }
    

    $context = New-ProcessingContext

    # Process each log item directly
    foreach ($log in $signInLogs) {
        # Exclude "On-Premises Directory Synchronization Service Account" user
        if ($log.UserDisplayName -ne "On-Premises Directory Synchronization Service Account" -and $null -ne $log) {
            try {
                Process-DeviceItem -Item $log -Context $context -Headers $Headers
                # Process-DeviceItem -Item $log -Headers $Headers
            }
            catch {
                Write-Error "Error processing item: $($_.Exception.Message)"
                Handle-Error -ErrorRecord $_
            }
        }
    }


    # # Stop the logging job when done
    # Stop-Job -Job $global:LogJob
    # Remove-Job -Job $global:LogJob





    # $jobs = @()

    # foreach ($log in $signInLogs) {
    # # Exclude "On-Premises Directory Synchronization Service Account" user
    # if ($log.UserDisplayName -ne "On-Premises Directory Synchronization Service Account" -and $null -ne $log) {
    # $jobs += Start-Job -ScriptBlock {
    # param ($log, $context, $headers)
            
    # try {
    # Process-DeviceItem -Item $log -Context $context -Headers $headers
    # }
    # catch {
    # Write-Error "Error processing item: $($_.Exception.Message)"
    # Handle-Error -ErrorRecord $_
    # }
    # } -ArgumentList $log, $context, $Headers
    # }
    # }

    # # Wait for all jobs to complete
    # $jobs | ForEach-Object {
    # $_ | Wait-Job
    # }

    # # Retrieve job results
    # $jobs | ForEach-Object {
    # Receive-Job -Job $_
    # Remove-Job -Job $_
    # }





    # $signInLogs | ForEach-Object -Parallel {
    # param (
    # $log,
    # $context,
    # $headers
    # )
    
    # # Exclude "On-Premises Directory Synchronization Service Account" user
    # if ($log.UserDisplayName -ne "On-Premises Directory Synchronization Service Account" -and $null -ne $log) {
    # try {
    # Process-DeviceItem -Item $log -Context $context -Headers $headers
    # }
    # catch {
    # Write-Error "Error processing item: $($_.Exception.Message)"
    # Handle-Error -ErrorRecord $_
    # }
    # }
    # } -ArgumentList $_, $using:context, $using:headers -ThrottleLimit 10
    


    # Remove null entries from the results list
    $context.Results = $context.Results | Where-Object { $_ -ne $null }

    # Return the results
    return $context.Results
}
#EndRegion '.\Public\Process-SignInLogs.ps1' 125