modules/HomeLab.Monitoring/Public/Costs.ps1
<#
.SYNOPSIS Gets the current cost of Azure resources. .DESCRIPTION Gets the current cost of Azure resources for the specified time period. .PARAMETER TimeGrain The time grain for the cost data. Valid values are 'Daily', 'Monthly', 'Yearly'. Default is 'Daily'. .PARAMETER StartDate The start date for the cost data. Default is the first day of the current month. .PARAMETER EndDate The end date for the cost data. Default is the current date. .EXAMPLE Get-CurrentCosts -TimeGrain 'Monthly' -StartDate (Get-Date).AddMonths(-3) -EndDate (Get-Date) #> function Get-CurrentCosts { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [ValidateSet('Daily', 'Monthly', 'Yearly')] [string]$TimeGrain = 'Daily', [Parameter(Mandatory = $false)] [datetime]$StartDate = (Get-Date -Day 1), [Parameter(Mandatory = $false)] [datetime]$EndDate = (Get-Date) ) begin { # Import required modules Import-Module HomeLab.Core Import-Module HomeLab.Azure # Get configuration $config = Get-Configuration # Log function start Write-Log -Message "Getting current costs" -Level INFO } process { try { # Check if Azure is connected if (-not (Test-AzureConnection)) { Connect-AzureAccount } # Format dates for the API $startDateStr = $StartDate.ToString("yyyy-MM-dd") $endDateStr = $EndDate.ToString("yyyy-MM-dd") # Get cost data $costData = Get-AzConsumptionUsageDetail -StartDate $startDateStr -EndDate $endDateStr # Process cost data based on time grain $groupedCosts = @() switch ($TimeGrain) { 'Daily' { $groupedCosts = $costData | Group-Object -Property { $_.UsageStart.Date } | ForEach-Object { [PSCustomObject]@{ Date = $_.Name TotalCost = ($_.Group | Measure-Object -Property PretaxCost -Sum).Sum Currency = $_.Group[0].Currency Details = $_.Group | Group-Object -Property InstanceName | ForEach-Object { [PSCustomObject]@{ ResourceName = $_.Name Cost = ($_.Group | Measure-Object -Property PretaxCost -Sum).Sum } } } } } 'Monthly' { $groupedCosts = $costData | Group-Object -Property { $_.UsageStart.ToString("yyyy-MM") } | ForEach-Object { [PSCustomObject]@{ Month = $_.Name TotalCost = ($_.Group | Measure-Object -Property PretaxCost -Sum).Sum Currency = $_.Group[0].Currency Details = $_.Group | Group-Object -Property InstanceName | ForEach-Object { [PSCustomObject]@{ ResourceName = $_.Name Cost = ($_.Group | Measure-Object -Property PretaxCost -Sum).Sum } } } } } 'Yearly' { $groupedCosts = $costData | Group-Object -Property { $_.UsageStart.Year } | ForEach-Object { [PSCustomObject]@{ Year = $_.Name TotalCost = ($_.Group | Measure-Object -Property PretaxCost -Sum).Sum Currency = $_.Group[0].Currency Details = $_.Group | Group-Object -Property InstanceName | ForEach-Object { [PSCustomObject]@{ ResourceName = $_.Name Cost = ($_.Group | Measure-Object -Property PretaxCost -Sum).Sum } } } } } } # Calculate total cost $totalCost = ($costData | Measure-Object -Property PretaxCost -Sum).Sum $currency = $costData[0].Currency # Create result object $result = [PSCustomObject]@{ TimeGrain = $TimeGrain StartDate = $StartDate EndDate = $EndDate TotalCost = $totalCost Currency = $currency CostBreakdown = $groupedCosts ResourceCosts = $costData | Group-Object -Property InstanceName | ForEach-Object { [PSCustomObject]@{ ResourceName = $_.Name Cost = ($_.Group | Measure-Object -Property PretaxCost -Sum).Sum } } | Sort-Object -Property Cost -Descending } return $result } catch { Write-Log -Message "Failed to get current costs: $_" -Level ERROR throw $_ } } end { # Log function end Write-Log -Message "Current costs retrieved successfully" -Level INFO } } <# .SYNOPSIS Gets a forecast of future costs. .DESCRIPTION Gets a forecast of future costs based on historical usage. .PARAMETER Months The number of months to forecast. Default is 3. .EXAMPLE Get-CostForecast -Months 6 #> function Get-CostForecast { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [int]$Months = 3 ) begin { # Import required modules Import-Module HomeLab.Core Import-Module HomeLab.Azure # Get configuration $config = Get-Configuration # Log function start Write-Log -Message "Getting cost forecast" -Level INFO } process { try { # Check if Azure is connected if (-not (Test-AzureConnection)) { Connect-AzureAccount } # Get historical cost data for the past 3 months $endDate = Get-Date $startDate = $endDate.AddMonths(-3) $historicalCosts = Get-CurrentCosts -TimeGrain 'Monthly' -StartDate $startDate -EndDate $endDate # Calculate average monthly cost $averageMonthlyCost = ($historicalCosts.CostBreakdown | Measure-Object -Property TotalCost -Average).Average # Generate forecast $forecastStartDate = $endDate.AddDays(1) $forecastEndDate = $forecastStartDate.AddMonths($Months) $forecastData = @() $currentDate = $forecastStartDate while ($currentDate -le $forecastEndDate) { $monthStart = Get-Date -Year $currentDate.Year -Month $currentDate.Month -Day 1 $monthEnd = $monthStart.AddMonths(1).AddDays(-1) # Apply some variation to make the forecast more realistic $variation = Get-Random -Minimum 0.9 -Maximum 1.1 $forecastCost = $averageMonthlyCost * $variation $forecastData += [PSCustomObject]@{ Month = $monthStart.ToString("yyyy-MM") StartDate = $monthStart EndDate = $monthEnd ForecastCost = $forecastCost Currency = $historicalCosts.Currency } $currentDate = $currentDate.AddMonths(1) } # Create result object $result = [PSCustomObject]@{ ForecastMonths = $Months StartDate = $forecastStartDate EndDate = $forecastEndDate TotalForecastCost = ($forecastData | Measure-Object -Property ForecastCost -Sum).Sum Currency = $historicalCosts.Currency MonthlyForecast = $forecastData AverageHistoricalCost = $averageMonthlyCost HistoricalData = $historicalCosts.CostBreakdown } return $result } catch { Write-Log -Message "Failed to get cost forecast: $_" -Level ERROR throw $_ } } end { # Log function end Write-Log -Message "Cost forecast generated successfully" -Level INFO } } <# .SYNOPSIS Exports a cost report to a file. .DESCRIPTION Exports a cost report to a file in the specified format. .PARAMETER Path The path where the report will be saved. If not specified, the report will be saved to the user's Documents folder. .PARAMETER Format The format of the report. Valid values are 'CSV', 'JSON', 'HTML'. Default is 'CSV'. .PARAMETER TimeGrain The time grain for the cost data. Valid values are 'Daily', 'Monthly', 'Yearly'. Default is 'Monthly'. .PARAMETER StartDate The start date for the cost data. Default is the first day of the current month. .PARAMETER EndDate The end date for the cost data. Default is the current date. .EXAMPLE Export-CostReport -Path "C:\Reports" -Format "HTML" -TimeGrain "Monthly" -StartDate (Get-Date).AddMonths(-6) -EndDate (Get-Date) #> function Export-CostReport { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string]$Path, [Parameter(Mandatory = $false)] [ValidateSet('CSV', 'JSON', 'HTML')] [string]$Format = 'CSV', [Parameter(Mandatory = $false)] [ValidateSet('Daily', 'Monthly', 'Yearly')] [string]$TimeGrain = 'Monthly', [Parameter(Mandatory = $false)] [datetime]$StartDate = (Get-Date -Day 1), [Parameter(Mandatory = $false)] [datetime]$EndDate = (Get-Date) ) begin { # Import required modules Import-Module HomeLab.Core Import-Module HomeLab.Azure # Get configuration $config = Get-Configuration # Log function start Write-Log -Message "Exporting cost report" -Level INFO # Set default path if not specified if (-not $Path) { $Path = [System.IO.Path]::Combine([Environment]::GetFolderPath('MyDocuments'), 'HomeLab', 'Reports') } # Create directory if it doesn't exist if (-not (Test-Path -Path $Path -PathType Container)) { New-Item -Path $Path -ItemType Directory -Force | Out-Null } } process { try { # Get cost data $costData = Get-CurrentCosts -TimeGrain $TimeGrain -StartDate $StartDate -EndDate $EndDate # Generate filename $timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $fileName = "CostReport_${TimeGrain}_${timestamp}" # Export based on format switch ($Format) { 'CSV' { $filePath = [System.IO.Path]::Combine($Path, "$fileName.csv") # Export cost breakdown $costData.CostBreakdown | Export-Csv -Path $filePath -NoTypeInformation # Export resource costs to a separate file $resourceCostsPath = [System.IO.Path]::Combine($Path, "${fileName}_ResourceCosts.csv") $costData.ResourceCosts | Export-Csv -Path $resourceCostsPath -NoTypeInformation } 'JSON' { $filePath = [System.IO.Path]::Combine($Path, "$fileName.json") $costData | ConvertTo-Json -Depth 5 | Out-File -FilePath $filePath -Encoding utf8 } 'HTML' { $filePath = [System.IO.Path]::Combine($Path, "$fileName.html") # Create HTML report $html = @" <!DOCTYPE html> <html> <head> <title>HomeLab Cost Report</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } h1, h2 { color: #0078d4; } table { border-collapse: collapse; width: 100%; margin-bottom: 20px; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } tr:nth-child(even) { background-color: #f9f9f9; } .summary { background-color: #e6f2ff; padding: 10px; border-radius: 5px; margin-bottom: 20px; } </style> </head> <body> <h1>HomeLab Cost Report</h1> <div class="summary"> <h2>Summary</h2> <p><strong>Time Period:</strong> $($StartDate.ToString("yyyy-MM-dd")) to $($EndDate.ToString("yyyy-MM-dd"))</p> <p><strong>Time Grain:</strong> $TimeGrain</p> <p><strong>Total Cost:</strong> $($costData.TotalCost.ToString("F2")) $($costData.Currency)</p> </div> <h2>Cost Breakdown</h2> <table> <tr> "@ # Add headers based on time grain switch ($TimeGrain) { 'Daily' { $html += "<th>Date</th>" } 'Monthly' { $html += "<th>Month</th>" } 'Yearly' { $html += "<th>Year</th>" } } $html += @" <th>Total Cost</th> <th>Currency</th> </tr> "@ # Add rows for cost breakdown foreach ($item in $costData.CostBreakdown) { $html += "<tr>" switch ($TimeGrain) { 'Daily' { $html += "<td>$($item.Date)</td>" } 'Monthly' { $html += "<td>$($item.Month)</td>" } 'Yearly' { $html += "<td>$($item.Year)</td>" } } $html += @" <td>$($item.TotalCost.ToString("F2"))</td> <td>$($item.Currency)</td> </tr> "@ } $html += @" </table> <h2>Resource Costs</h2> <table> <tr> <th>Resource Name</th> <th>Cost</th> </tr> "@ # Add rows for resource costs foreach ($item in $costData.ResourceCosts) { $html += @" <tr> <td>$($item.ResourceName)</td> <td>$($item.Cost.ToString("F2")) $($costData.Currency)</td> </tr> "@ } $html += @" </table> <p><em>Report generated on $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")</em></p> </body> </html> "@ # Save HTML to file $html | Out-File -FilePath $filePath -Encoding utf8 } } Write-Log -Message "Cost report exported to $filePath" -Level INFO return $filePath } catch { Write-Log -Message "Failed to export cost report: $_" -Level ERROR throw $_ } } end { # Log function end Write-Log -Message "Cost report export completed" -Level INFO } } |