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" }

        # Create a new Result object
        $splatNewResult = @{
            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

        }
        
        $result = New-Result @splatNewResult
        
        # Add the result to the context
        $Context.Results.Add($result)

        Write-EnhancedLog -Message "Successfully added result for device: $deviceName for user: $($Item.UserDisplayName)" -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' 63
#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" -ForegroundColor Cyan

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

    $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." -ForegroundColor Green
                return "Present"
            } else {
                Write-EnhancedLog -Message "Device is absent in Intune." -ForegroundColor Yellow
                return "Absent"
            }
        } else {
            Write-EnhancedLog -Message "Received empty response from Intune API." -ForegroundColor Yellow
            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 "DEBUG"

        $latestJsonFile = Find-LatestJsonFile -Directory $subFolderPath

        if ($latestJsonFile) {
            Write-EnhancedLog -Message "Latest JSON file found: $latestJsonFile" -Level "DEBUG"
            $signInLogs = Load-SignInLogs -JsonFilePath $latestJsonFile
            if ($signInLogs.Count -gt 0) {
                Write-EnhancedLog -Message "Sign-in logs found in $latestJsonFile. Starting to process it" -Level "INFO"
                # Process-AllDevices -Json $signInLogs -Headers $Headers
                return $signInLogs
            } else {
                Write-EnhancedLog -Message "No sign-in logs found in $latestJsonFile." -Level "WARNING"
                return @()
            }
        } 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' 58
#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 'Debug'
    $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()
            }

            $signInLog = [PSCustomObject]@{
                UserDisplayName = $element.GetProperty("userDisplayName").GetString()
                UserId          = $element.GetProperty("userId").GetString()
                DeviceDetail    = $deviceDetail
            }

            $signInLogs.Add($signInLog)
        }

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

    return $signInLogs
}

# # Example usage
# $jsonFilePath = "path_to_your_json_file.json"
# $signInLogs = Load-SignInLogs -JsonFilePath $jsonFilePath
# Write-Output "Sign-In Logs: $($signInLogs | Format-Table -AutoSize)"

#EndRegion '.\Public\Load-SignInLogs.ps1' 47
#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 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
            }
        } 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.ps1' 83
#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
    }

    # Display the count of loaded sign-in logs
    Write-Host "Loaded $($signInLogs.Count) sign-in logs."

    # Debugging: Print the first sign-in log entry
    if ($signInLogs.Count -gt 0) {
        $firstSignInLog = $signInLogs[0]
        Write-Host "First sign-in log entry:"
        Write-Host "UserDisplayName: $($firstSignInLog.UserDisplayName)"
        Write-Host "UserId: $($firstSignInLog.UserId)"
        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
            } catch {
                Write-Error "Error processing item: $($_.Exception.Message)"
                Handle-Error -ErrorRecord $_
            }
        }
    }

    # 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' 53