private/Get-AzVMUptime.ps1

function Get-AzVMUptime {
    param(
        [Parameter(Mandatory)]
        [string] $VMName,
        [Parameter(Mandatory)]
        [string] $ResourceGroupName,
        [Parameter(Mandatory)]
        [array] $vmList,
        [Parameter()]
        [int] $days = 1,
        [Parameter()]
        [int] $offsetDays = 1
    )
    
    begin {
        Write-Verbose "--> Starting uptime calculation for VM: $VMName" -Verbose
        # Validate dates
        $days = ($days + $offsetDays)
        $Date = (Get-Date).ToUniversalTime()
        $StartDate = $date.AddDays(-$days)
        $EndDate = $date.AddDays(-$offsetDays)
        $PSBoundParameters | ConvertTo-Json -Depth 1 | Write-Verbose 
        $stateRef = @{}
        $stateRef.Add('Start Virtual Machine','running')
        $stateRef.Add('Deallocate Virtual Machine','stopped')
    }
    
    process {
        try {
            # $vm = $vmlist | Where-Object {$_.name -eq $VMName -and $_.ResourceGroupName -eq $ResourceGroupName}
            
            $vmStatus = $vmlist | Where-Object {$_.name -eq $VMName -and $_.ResourceGroupName -eq $ResourceGroupName}
            $resourceId = $vmStatus.Id
            $currentPowerState = $vmStatus.PowerState

            # Query Activity Log for power events
            Write-Verbose "Querying Activity Log from $StartDate to $EndDate..."

            $activityLogs = Get-AzActivityLog -ResourceId $resourceId -StartTime $StartDate -EndTime $EndDate -ErrorAction Stop | Sort-Object EventTimestamp 
            $created = $activityLogs | Where-Object {$_.OperationName -eq 'Create or Update Virtual Machine' -and $_.SubStatus -eq 'Created (HTTP Status Code: 201)'} 
            if ($created) {
                $createdDate = $created[0].EventTimestamp
                if ($createdDate -lt $StartDate) {
                    # Write-Verbose "VM was created on $createdDate, adjusting StartDate from $StartDate to $createdDate"
                    $createdDate = $created[0].EventTimestamp
                }
            }
            $activityLogs = $activityLogs | Where-Object {$_.Status -eq 'Succeeded'}

            $statusArray = @('Start Virtual Machine','Deallocate Virtual Machine')
            $powerStateEvents = $activityLogs | Where-Object {$_.OperationName -in $statusArray}

            Write-Verbose "Found $($powerStateEvents.Count) power state events"
            
            # Build timeline of state changes
            $stateChanges = @() 

            if ($created) {
                    $stateObj= [PSCustomObject] @{
                    Timestamp = $created.EventTimestamp
                    State     = 'running'
                    Operation = $created.OperationName
                }
                $stateChanges += $stateObj
            }
            
            foreach ($log in $powerStateEvents) {
                # Only add if operation was successful
                
                $stateObj= [PSCustomObject] @{
                    Timestamp = $log.EventTimestamp
                    State     = $stateRef[$log.OperationName]
                    Operation = $log.OperationName
                }
                $stateChanges += $stateObj
            }
            
            # If no events found, use current state and assume it was the same throughout
            if ($stateChanges.Count -eq 0) {
                Write-Verbose "No state changes found in Activity Log, checking current state..."
                if ($currentPowerState -eq 'VM running') {
                    $initialState = 'running'
                } else {
                    $initialState = 'stopped'
                }
               
                Write-Warning "No power state changes detected in Activity Log for the specified period. Using current state: $initialState"
            } elseif ($createdDate) {
                $initialState = 'stopped'
            } else {
                # Get state before first event by looking at what action occurred
                # If first action is 'start', VM was stopped before; if 'stop/deallocate', it was running
                $initialState = ($stateRef.GetEnumerator() | Where-Object {$_.Value -ne $stateChanges[0].State}).value

                Write-Verbose "Inferred initial state at $StartDate : $initialState"
            }
            
            # Calculate uptime by walking through state changes
            $totalSeconds = ($EndDate - $StartDate).TotalSeconds
            $runningSeconds = 0
            $currentState = $initialState
            $lastTimestamp = $StartDate
            
            if ($stateChanges) {
                foreach ($change in $stateChanges) {
                    # Calculate duration in current state
                    $duration = ($change.Timestamp - $lastTimestamp).TotalSeconds
                    
                    if ($currentState -eq 'running') {
                        $runningSeconds += $duration
                    }
                    
                    # Update state
                    $currentState = ($stateRef.GetEnumerator() | Where-Object {$_.Value -ne $currentState}).value
                    $lastTimestamp = $change.Timestamp
                }
                # Add final segment from last state change to EndDate
                $finalDuration = ($EndDate - $lastTimestamp).TotalSeconds
                if ($currentState -eq 'running') {
                    $runningSeconds += $finalDuration
                }
            } else {
                $runningSeconds = ($EndDate - $StartDate).TotalSeconds
            }

            # Calculate percentages and hours
            $uptimePercentage = if ($totalSeconds -gt 0) { 
                [math]::Round(($runningSeconds / $totalSeconds) * 100, 2) 
            } else { 
                0 
            }
            
            $totalHours = [math]::Round($totalSeconds / 3600, 2)
            $runningHours = [math]::Round($runningSeconds / 3600, 2)
            $stoppedHours = [math]::Round(($totalSeconds - $runningSeconds) / 3600, 2)
            
            # Return result object
            [PSCustomObject]@{
                VMName            = $VMName
                ResourceGroup     = $ResourceGroupName
                StartDate         = $StartDate
                EndDate           = $EndDate
                UptimePercentage  = $uptimePercentage
                TotalHours        = $totalHours
                RunningHours      = $runningHours
                StoppedHours      = $stoppedHours
                StateChanges      = $stateChanges.Count
                InitialState      = $initialState
                FinalState        = $currentState
            }
        }
        catch {
            Write-Error "Failed to calculate uptime for VM '$VMName': $_"
            throw
        }
    }
}