Public/Get-EFEndpointInventory.ps1

function Get-EFEndpointInventory {
    <#
    .SYNOPSIS
    Collects normalized hardware, operating system, network, and security inventory.
 
    .DESCRIPTION
    Collects read-only local Windows inventory without querying Win32_Product. Features
    that are not installed or supported are represented as null and recorded in Errors,
    allowing collection to continue on heterogeneous fleets.
 
    .PARAMETER IncludeSoftware
    Includes registry-based installed software inventory in the returned object.
 
    .PARAMETER IncludeUser
    Includes the currently reported interactive user. It is excluded by default to
    minimize collection of user-identifying data.
 
    .PARAMETER NoProgress
    Suppresses the progress display for non-interactive automation hosts.
 
    .EXAMPLE
    Get-EFEndpointInventory
 
    .EXAMPLE
    Get-EFEndpointInventory -IncludeSoftware | Export-EFEndpointReport -Path .\inventory.json
 
    .OUTPUTS
    EndpointForge.EndpointInventory
 
    .LINK
    Get-EFEndpointSummary
    #>

    [CmdletBinding()]
    param(
        [switch]$IncludeSoftware,
        [switch]$IncludeUser,
        [switch]$NoProgress
    )

    $null = Test-EFWindows -Throw
    $correlationId = [guid]::NewGuid().ToString()
    $errors = [Collections.Generic.List[string]]::new()
    Write-EFLog -Message 'Endpoint inventory collection started.' -CorrelationId $correlationId
    if (-not $NoProgress) {
        Write-Progress -Id 1101 -Activity 'EndpointForge inventory' -Status 'Collecting operating system and hardware data' -PercentComplete 5
    }

    $os = $null
    $computerSystem = $null
    $bios = $null
    $processor = $null
    $systemDisk = $null

    try { $os = Invoke-EFRetry -Operation 'Operating system inventory' -ScriptBlock { Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop } }
    catch { $errors.Add("OperatingSystem: $($_.Exception.Message)") }
    try { $computerSystem = Invoke-EFRetry -Operation 'Computer system inventory' -ScriptBlock { Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop } }
    catch { $errors.Add("ComputerSystem: $($_.Exception.Message)") }
    try { $bios = Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop }
    catch { $errors.Add("BIOS: $($_.Exception.Message)") }
    try { $processor = @(Get-CimInstance -ClassName Win32_Processor -ErrorAction Stop)[0] }
    catch { $errors.Add("Processor: $($_.Exception.Message)") }
    try {
        $escapedDrive = ([string]$env:SystemDrive).Replace("'", "''")
        $systemDisk = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='$escapedDrive'" -ErrorAction Stop
    }
    catch { $errors.Add("SystemDisk: $($_.Exception.Message)") }

    if (-not $NoProgress) {
        Write-Progress -Id 1101 -Activity 'EndpointForge inventory' -Status 'Collecting network configuration' -PercentComplete 35
    }

    $fqdn = $env:COMPUTERNAME
    try {
        $ipProperties = [Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()
        if (-not [string]::IsNullOrWhiteSpace($ipProperties.DomainName)) {
            $fqdn = "$env:COMPUTERNAME.$($ipProperties.DomainName)"
        }
    }
    catch { $errors.Add("FQDN: $($_.Exception.Message)") }

    $network = @()
    if ($null -ne (Get-Command -Name Get-NetIPConfiguration -ErrorAction SilentlyContinue)) {
        try {
            $network = @(
                Get-NetIPConfiguration -ErrorAction Stop |
                    Where-Object { $_.NetAdapter.Status -eq 'Up' } |
                    ForEach-Object {
                        [pscustomobject]@{
                            InterfaceAlias = $_.InterfaceAlias
                            MacAddress     = $_.NetAdapter.MacAddress
                            IPv4Address    = @($_.IPv4Address | ForEach-Object IPAddress)
                            IPv6Address    = @($_.IPv6Address | ForEach-Object IPAddress)
                            DefaultGateway = @($_.IPv4DefaultGateway | ForEach-Object NextHop)
                            DnsServer      = @($_.DNSServer.ServerAddresses)
                        }
                    }
            )
        }
        catch { $errors.Add("Network: $($_.Exception.Message)") }
    }

    if (-not $NoProgress) {
        Write-Progress -Id 1101 -Activity 'EndpointForge inventory' -Status 'Collecting hardware-backed security state' -PercentComplete 50
    }

    $secureBoot = $null
    if ($null -ne (Get-Command -Name Confirm-SecureBootUEFI -ErrorAction SilentlyContinue)) {
        try { $secureBoot = [bool](Confirm-SecureBootUEFI -ErrorAction Stop) }
        catch { $errors.Add("SecureBoot: $($_.Exception.Message)") }
    }

    $tpmInfo = $null
    if ($null -ne (Get-Command -Name Get-Tpm -ErrorAction SilentlyContinue)) {
        try {
            $tpm = Get-Tpm -ErrorAction Stop
            if (-not (Test-EFPropertyPresent -InputObject $tpm -Name 'TpmPresent')) {
                throw 'TPM status was not returned. Run PowerShell as Administrator for TPM inventory.'
            }
            $tpmInfo = [pscustomobject]@{
                Present          = [bool](Get-EFPropertyValue -InputObject $tpm -Name 'TpmPresent')
                Ready            = [bool](Get-EFPropertyValue -InputObject $tpm -Name 'TpmReady')
                Enabled          = [bool](Get-EFPropertyValue -InputObject $tpm -Name 'TpmEnabled')
                Activated        = [bool](Get-EFPropertyValue -InputObject $tpm -Name 'TpmActivated')
                ManagedAuthLevel = [string](Get-EFPropertyValue -InputObject $tpm -Name 'ManagedAuthLevel')
            }
        }
        catch { $errors.Add("TPM: $($_.Exception.Message)") }
    }

    $bitLockerInfo = $null
    if ($null -ne (Get-Command -Name Get-BitLockerVolume -ErrorAction SilentlyContinue)) {
        try {
            $bitLocker = Get-BitLockerVolume -MountPoint $env:SystemDrive -ErrorAction Stop
            $bitLockerInfo = [pscustomobject]@{
                MountPoint       = [string]$bitLocker.MountPoint
                VolumeStatus     = [string]$bitLocker.VolumeStatus
                ProtectionStatus = [string]$bitLocker.ProtectionStatus
                EncryptionMethod = [string]$bitLocker.EncryptionMethod
                EncryptionPercentage = [int]$bitLocker.EncryptionPercentage
            }
        }
        catch { $errors.Add("BitLocker: $($_.Exception.Message)") }
    }

    if (-not $NoProgress) {
        Write-Progress -Id 1101 -Activity 'EndpointForge inventory' -Status 'Collecting Defender and firewall state' -PercentComplete 70
    }

    $defenderInfo = $null
    if ($null -ne (Get-Command -Name Get-MpComputerStatus -ErrorAction SilentlyContinue)) {
        try {
            $defender = Get-MpComputerStatus -ErrorAction Stop
            $defenderInfo = [pscustomobject]@{
                AntivirusEnabled          = [bool]$defender.AntivirusEnabled
                AntispywareEnabled        = [bool]$defender.AntispywareEnabled
                RealTimeProtectionEnabled = [bool]$defender.RealTimeProtectionEnabled
                BehaviorMonitorEnabled    = [bool]$defender.BehaviorMonitorEnabled
                AntivirusSignatureVersion = [string]$defender.AntivirusSignatureVersion
                AntivirusSignatureAge     = [int]$defender.AntivirusSignatureAge
            }
        }
        catch { $errors.Add("Defender: $($_.Exception.Message)") }
    }

    $firewallInfo = @()
    if ($null -ne (Get-Command -Name Get-NetFirewallProfile -ErrorAction SilentlyContinue)) {
        try {
            $firewallInfo = @(
                Get-NetFirewallProfile -ErrorAction Stop | ForEach-Object {
                    [pscustomobject]@{
                        Name    = [string]$_.Name
                        Enabled = [bool]$_.Enabled
                    }
                }
            )
        }
        catch { $errors.Add("Firewall: $($_.Exception.Message)") }
    }

    $lastBoot = Get-EFPropertyValue -InputObject $os -Name 'LastBootUpTime'
    $uptime = if ($null -ne $lastBoot) { [DateTime]::Now - [DateTime]$lastBoot } else { $null }
    $diskSize = Get-EFPropertyValue -InputObject $systemDisk -Name 'Size'
    $diskFree = Get-EFPropertyValue -InputObject $systemDisk -Name 'FreeSpace'
    $diskPercent = if ($null -ne $diskSize -and [double]$diskSize -gt 0) {
        [math]::Round(([double]$diskFree / [double]$diskSize) * 100, 1)
    } else { $null }

    if (-not $NoProgress) {
        $softwareStatus = if ($IncludeSoftware) { 'Collecting installed software' } else { 'Finalizing endpoint inventory' }
        Write-Progress -Id 1101 -Activity 'EndpointForge inventory' -Status $softwareStatus -PercentComplete 90
    }

    $interactiveUser = if ($IncludeUser) { Get-EFPropertyValue -InputObject $computerSystem -Name 'UserName' } else { $null }
    $software = if ($IncludeSoftware) { @(Get-EFInstalledSoftware) } else { $null }

    $osCaption = Get-EFPropertyValue -InputObject $os -Name 'Caption'
    $osBuild = Get-EFPropertyValue -InputObject $os -Name 'BuildNumber'
    $deviceModel = Get-EFPropertyValue -InputObject $computerSystem -Name 'Model'
    $serialNumber = Get-EFPropertyValue -InputObject $bios -Name 'SerialNumber'
    $uptimeDays = if ($null -ne $uptime) { [math]::Round($uptime.TotalDays, 2) } else { $null }

    $inventory = [pscustomobject]@{
        PSTypeName       = 'EndpointForge.EndpointInventory'
        SchemaVersion    = '1.0'
        ComputerName     = $env:COMPUTERNAME
        Fqdn             = $fqdn
        CapturedAtUtc    = [DateTime]::UtcNow
        CorrelationId    = $correlationId
        OperatingSystemName = $osCaption
        OperatingSystemBuild = $osBuild
        DeviceModel      = $deviceModel
        SerialNumber     = $serialNumber
        UptimeDays       = $uptimeDays
        SystemDriveFreePercent = $diskPercent
        DataStatus       = if ($null -eq $os -and $null -eq $computerSystem) { 'Failed' } elseif ($errors.Count -gt 0) { 'Partial' } else { 'Complete' }
        CollectionErrorCount = $errors.Count
        InteractiveUser  = $interactiveUser
        ComputerSystem   = [pscustomobject]@{
            Manufacturer  = Get-EFPropertyValue -InputObject $computerSystem -Name 'Manufacturer'
            Model         = $deviceModel
            SerialNumber  = $serialNumber
            TotalMemoryGB = if ($null -ne (Get-EFPropertyValue -InputObject $computerSystem -Name 'TotalPhysicalMemory')) {
                [math]::Round([double](Get-EFPropertyValue -InputObject $computerSystem -Name 'TotalPhysicalMemory') / 1GB, 2)
            } else { $null }
            Processor     = Get-EFPropertyValue -InputObject $processor -Name 'Name'
            LogicalProcessors = Get-EFPropertyValue -InputObject $computerSystem -Name 'NumberOfLogicalProcessors'
            BiosVersion   = @((Get-EFPropertyValue -InputObject $bios -Name 'BIOSVersion')) -join '; '
        }
        OperatingSystem  = [pscustomobject]@{
            Caption        = $osCaption
            Version        = Get-EFPropertyValue -InputObject $os -Name 'Version'
            BuildNumber    = $osBuild
            Architecture   = Get-EFPropertyValue -InputObject $os -Name 'OSArchitecture'
            InstallDate    = Get-EFPropertyValue -InputObject $os -Name 'InstallDate'
            LastBootTime   = $lastBoot
            UptimeDays     = $uptimeDays
        }
        SystemDrive      = [pscustomobject]@{
            Drive              = $env:SystemDrive
            SizeGB             = if ($null -ne $diskSize) { [math]::Round([double]$diskSize / 1GB, 2) } else { $null }
            FreeGB             = if ($null -ne $diskFree) { [math]::Round([double]$diskFree / 1GB, 2) } else { $null }
            FreeSpacePercent   = $diskPercent
        }
        Network          = $network
        Security         = [pscustomobject]@{
            SecureBoot = $secureBoot
            Tpm        = $tpmInfo
            BitLocker  = $bitLockerInfo
            Defender   = $defenderInfo
            Firewall   = $firewallInfo
        }
        Software         = $software
        Errors           = @($errors)
    }

    Write-EFLog -Message 'Endpoint inventory collection completed.' `
        -Level $(if ($errors.Count -eq 0) { 'Information' } else { 'Warning' }) `
        -CorrelationId $correlationId -Data @{ errorCount = $errors.Count; includeSoftware = [bool]$IncludeSoftware }

    if (-not $NoProgress) {
        Write-Progress -Id 1101 -Activity 'EndpointForge inventory' -Completed
    }

    return $inventory
}