CheckInvoicing/Get-M365LicenseInvoicing.ps1
$AboCSVPath = "CheckInvoicing\ABO.CSV" if (-not (Test-Path $AboCSVPath)) { Write-Host "ABO.CSV file not found at path: $AboCSVPath" -ForegroundColor Red Write-Host "Please place the file in the correct location. (Export as Excel from KL)" -ForegroundColor Yellow exit } $createdDate = (Get-Item $AboCSVPath).CreationTime $threeDaysAgo = (Get-Date).AddDays(-3) if ($createdDate -lt $threeDaysAgo) { Write-Host "ABO.CSV file is older than 3 days. Please update it." -ForegroundColor Red exit } $ignoredKL = @(602948,600410,211475) $TimeStart = Get-Date Write-Host "Script started at $TimeStart" $customerLicenses = @() Write-Host "Getting all subscriptions..." $allSubscriptions = Get-CuranetCustomerSubscriptions -Account 3850 #| Where-Object { $_.subscriptionRef -eq 211475 } Write-Host "Found $($allSubscriptions.Count) subscriptions in Curanet API" Write-Host "Getting all licenses..." Write-Host "This may take a while..." -ForegroundColor Yellow $i = 0 foreach ($subscription in $allSubscriptions) { $i++ if ($i -gt 300) { #break } if ($subscription.state -ne 'Running') { continue } $kl = ($subscription.subscriptionRef).Trim() if ($kl -match 'INTERN*') { continue } if ($kl -in $ignoredKL) { continue } $licenses = Get-CuranetM365Licenses -SubscriptionId $subscription.id -Account 3850 # Group licenses by title and term $licenseArray = @{} foreach ($license in $licenses) { # Skip if price is 0 if ($license.unitPrice -eq 0) { continue } $title = $license.title $term = $license.termDuration # Initialize the key if it doesn't exist if (-not $licenseArray.ContainsKey($title)) { $licenseArray[$title] = @{} } # Initialize the term if it doesn't exist if (-not $licenseArray[$title].ContainsKey($term)) { $licenseArray[$title][$term] = 0 } $licenseArray[$title][$term] += $license.quantity } if ($licenseArray.Count -eq 0) { continue } # Add the subscription's grouped licenses to the result $customerLicenses += [pscustomobject]@{ KL = $kl Licenses = $licenseArray } } $customerKLSubscriptions = ConvertFrom-Csv (Get-Content -Path "CheckInvoicing\ABO.CSV") -Delimiter ";" $customerKLSubscriptions = $customerKLSubscriptions $skuMapping = @{ "Microsoft 365 Business Standard-OneYear" = @(170004, 170017) "Microsoft 365 Business Standard-OneMonth" = @(170003, 170016) "Microsoft 365 Business Premium-OneYear" = @(170006, 170019) "Microsoft 365 Business Premium-OneMonth" = @(170005, 170018) "Microsoft 365 Business Basic-OneYear" = @(170002, 170015,170011) "Microsoft 365 Business Basic-OneMonth" = @(170000, 170014,170010) "Microsoft 365 F1-OneYear" = @(170129) "Microsoft 365 F1-OneMonth" = @(170130) "Microsoft 365 Audio Conferencing-OneYear" = @(170071) "Microsoft 365 Audio Conferencing-OneMonth" = @(170070) "Exchange Online (Plan 1)-OneYear" = @(170009, 170024) "Exchange Online (Plan 1)-OneMonth" = @(170081, 170096) "Exchange Online (Plan 2)-OneYear" = @(170025, 170120) "Exchange Online (Plan 2)-OneMonth" = @(170140) "Microsoft 365 Copilot-OneYear" = @(170133) "Power Automate per user plan-OneYear" = @(170067) "Power Automate per user plan-OneMonth" = @(170066) "Microsoft 365 Apps for business-OneYear" = @(170021) "Microsoft 365 Apps for business-OneMonth" = @(170020) "OneDrive for business (Plan 1)-OneYear" = @(170055,170119) "OneDrive for business (Plan 1)-OneMonth" = @(170054) "OneDrive for business (Plan 2)-OneYear" = @(170121) "Microsoft 365 Apps for enterprise-OneYear" = @(170023) "Microsoft 365 Apps for enterprise-OneMonth" = @(170022) "Power Automate Premium-OneYear" = @(170139) "Power Automate Premium-OneMonth" = @(170138) "Office 365 E3-OneYear" = @(170029, 170008) "Office 365 E3-OneMonth" = @(170028, 170007) "Office 365 E3 EEA (no Teams)-OneYear" = @(170125) "Office 365 E3 EEA (no Teams)-OneMonth" = @(170126) "Visio Plan 1-OneYear" = @(170051) "Visio Plan 2-OneYear" = @(170053) "Visio Plan 2-OneMonth" = @(170052) "Microsoft 365 Business Premium (Nonprofit Staff Pricing)-OneYear" = @(170152,170145) "Power BI Pro (Non-Profit Pricing)-OneYear" = @(170146) "Office 365 E1-OneYear" = @(170027) "Office 365 E1-OneMonth" = @(170026) "Planner and Project Plan 3-OneYear" = @(170039) "Planner and Project Plan 3-OneMonth" = @(170038) "Power BI Pro-OneYear" = @(170043) "Power BI Pro-OneMonth" = @(170042) "Microsoft 365 F3-OneMonth" = @(170137) "Microsoft 365 E3-OneYear" = @(170033) "Microsoft 365 E3-OneMonth" = @(170032) "Microsoft Entra ID P1-OneYear" = @(170047) "Microsoft Entra ID P1-OneMonth" = @(170046) "Microsoft Teams Essentials-OneYear" = @(170094) "Microsoft Teams Essentials-OneMonth" = @(170095) "Planner Plan 1-OneYear" = @(170142,170037) "SharePoint (Plan 2)-OneYear" = @(170069) "Power Apps per app plan-OneYear" = @(170148,170080) "Office 365 A3 (Education Faculty Pricing)-OneYear" = @(170059) "Office 365 A3 (Education Faculty Pricing)-OneMonth" = @(170058) "Microsoft 365 A3 (Education Student Pricing)-OneYear" = @(170063) "Microsoft 365 A3 (Education Student Pricing)-OneMonth" = @(170062) "Microsoft Intune Plan 1 Devices-OneYear" = @(170075) "Microsoft Intune Plan 1 Devices-OneMonth" = @(170074) "Power Automate unattended RPA add-on-OneYear" = @(170078) "Microsoft Teams Premium-OneYear" = @(170123) "Power BI Premium Per User-OneYear" = @(170045) "Power BI Pro (Non-Profit Pricing)-OneMonth" = @(170146) "Microsoft Teams Phone Standard-OneYear" = @(170073) } $transformedKLSubscriptions = @() foreach ($klGroup in $customerKLSubscriptions | Group-Object -Property 'Kundenr.') { $kl = $klGroup.Name if ($ignoredKL -contains $kl) { continue } $licenseArray = @{} foreach ($subscription in $klGroup.Group) { $sku = $subscription.'Varenr.' if (-not $skuMapping.Values | Where-Object { $_ -contains $sku }) { continue } $quantity = [int]$subscription.'Faktureret antal' if([int]$subscription.'Antal ialt' -gt $quantity) { $quantity = [int]$subscription.'Antal ialt' } $titleTerm = $skuMapping.GetEnumerator() | Where-Object { $_.Value -contains $sku } if ($titleTerm) { $keyParts = $titleTerm.Key -split '-' $title = $keyParts[0] $term = $keyParts[1] # Initialize the title and term in the license array if not already present if (-not $licenseArray.ContainsKey($title)) { $licenseArray[$title] = @{} } if (-not $licenseArray[$title].ContainsKey($term)) { $licenseArray[$title][$term] = 0 } # Add the quantity to the appropriate group if its not expired $expired = $false if(($subscription.'Opsagt pr. dato').Length -gt 2) { if(($subscription.'N�ste hovedfaktureringsdato').Length -gt 2) { $linieUdloeb = [DateTime]::ParseExact($subscription.'N�ste hovedfaktureringsdato', "dd-MM-yyyy", $null) } $cancelledDate = [DateTime]::ParseExact($subscription.'Opsagt pr. dato', "dd-MM-yyyy", $null) $latestDate = $cancelledDate if($linieUdloeb -gt $cancelledDate) { $latestDate = $linieUdloeb } $expired = (Get-Date) -gt $latestDate } if($expired) { continue } $licenseArray[$title][$term] += $quantity } } # Add the transformed data to the result $transformedKLSubscriptions += [pscustomobject]@{ KL = $kl Licenses = $licenseArray } } # Compare $customerLicenses with $transformedKLSubscriptions $differences = @() foreach ($customerLicense in $customerLicenses) { $kl = $customerLicense.KL $registeredData = $transformedKLSubscriptions | Where-Object { $_.KL -eq $kl } if (-not $registeredData) { # Log missing KL in registered data $differences += [pscustomobject]@{ KL = $kl Title = "N/A" Term = "N/A" APIQuantity = "N/A" RegisteredQuantity = "Missing KL in registered data" } continue } foreach ($title in $customerLicense.Licenses.Keys) { foreach ($term in $customerLicense.Licenses[$title].Keys) { $apiQuantity = $customerLicense.Licenses[$title][$term] if ($registeredData.Licenses.ContainsKey($title) -and $registeredData.Licenses[$title].ContainsKey($term)) { $registeredQuantity = $registeredData.Licenses[$title][$term] if ($apiQuantity -gt $registeredQuantity) { # Log mismatch in quantities $differences += [pscustomobject]@{ KL = $kl Title = $title Term = $term APIQuantity = $apiQuantity RegisteredQuantity = $registeredQuantity } } } else { # Log missing title/term in registered data $differences += [pscustomobject]@{ KL = $kl Title = $title Term = $term APIQuantity = $apiQuantity RegisteredQuantity = "Missing in registered data" } } } } } # Output differences if ($differences.Count -gt 0) { Write-Host "Differences found:" $differences | Format-Table -AutoSize # Optionally export differences to a CSV file $differences | Export-Excel -Path "CheckInvoicing\M365Licenses.xlsx" -AutoSize -TableStyle Dark1 -Title "Differences in M365 License Invoicing" -WorksheetName "Differences" -ClearSheet } else { Write-Host "No differences found. All data matches. Something might be wrong, lol!" } $TimeEnd = Get-Date $TimeSpan = $TimeEnd - $TimeStart Write-Host "Script execution time: $($TimeSpan.Minutes) minutes and $($TimeSpan.Seconds) seconds" |