From 3b9ade1d93fe63e9561b163c17bf6a63308b3986 Mon Sep 17 00:00:00 2001 From: Eric Harless Date: Thu, 22 Jan 2026 20:27:35 -0500 Subject: [PATCH 1/2] Fix state mutation bug in Get-AutotaskAPIResource - Fixed line 57 which mutated global $Script:Queries table - Changed from direct property mutation to object cloning - Prevents POST operations from failing with 404 after GET calls - Affects all resources with /query endpoints (Products, etc.) - Backward compatible - identical behavior without side effects - Includes comprehensive test suite (Test-ModuleFix.ps1) --- Public/Get-AutotaskAPIResource.ps1 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Public/Get-AutotaskAPIResource.ps1 b/Public/Get-AutotaskAPIResource.ps1 index 0e3f097..2263cee 100644 --- a/Public/Get-AutotaskAPIResource.ps1 +++ b/Public/Get-AutotaskAPIResource.ps1 @@ -54,7 +54,15 @@ function Get-AutotaskAPIResource { $headers = $Script:AutotaskAuthHeader $Script:Index = $Script:Queries | Group-Object Index -AsHashTable -AsString $ResourceURL = @(($Script:Index[$resource] | Where-Object { $_.Get -eq $resource }))[0] - $ResourceURL.name = $ResourceURL.name.replace("/query", "/{PARENTID}") + # BUGFIX: Clone the object instead of mutating the global $Script:Queries table + $ResourceURL = [PSCustomObject]@{ + Index = $ResourceURL.Index + Name = $ResourceURL.Name.replace("/query", "/{PARENTID}") + Get = $ResourceURL.Get + Post = $ResourceURL.Post + Patch = $ResourceURL.Patch + Delete = $ResourceURL.Delete + } # Fix path to InvoicePDF URL, must be unique vs. /Invoices in Swagger file $ResourceURL.name = $ResourceURL.name.replace("V1.0/InvoicePDF", "V1.0/Invoices/{id}/InvoicePDF") if ($SimpleSearch) { From 8c9987fd779fc85660cfaf4736454251200efae6 Mon Sep 17 00:00:00 2001 From: Eric Harless Date: Thu, 22 Jan 2026 20:28:37 -0500 Subject: [PATCH 2/2] Add comprehensive test suite for state mutation fix - Test-ModuleFix.ps1 validates the bug fix - Tests verify $Script:Queries table remains unchanged after GET - Tests confirm POST operations succeed after GET operations - Includes before/after comparison of Queries table - All tests passed (product ID 29683482 created successfully) --- Test-ModuleFix.ps1 | 133 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 Test-ModuleFix.ps1 diff --git a/Test-ModuleFix.ps1 b/Test-ModuleFix.ps1 new file mode 100644 index 0000000..20bc779 --- /dev/null +++ b/Test-ModuleFix.ps1 @@ -0,0 +1,133 @@ +<# +.SYNOPSIS +Test script to verify the AutoTaskAPI module fix for state mutation bug + +.DESCRIPTION +This script tests that Get-AutotaskAPIResource no longer corrupts the +$Script:Queries table, allowing New-AutotaskAPIResource to work correctly. + +The bug: Get-AutotaskAPIResource was mutating the global $Script:Queries table +by replacing "/query" with "/{PARENTID}" directly on the shared object. + +The fix: Clone the object before modification to avoid mutating shared state. +#> + +[CmdletBinding()] +param() + +Write-Host "`n==================================================================" -ForegroundColor Cyan +Write-Host "AutoTaskAPI Module Fix Verification Test" -ForegroundColor Cyan +Write-Host "==================================================================`n" -ForegroundColor Cyan + +# Import the FIXED module from local directory +Write-Host "[1] Importing FIXED module from local directory..." -ForegroundColor Yellow +$modulePath = "$PSScriptRoot" +Remove-Module AutoTaskAPI -ErrorAction SilentlyContinue +Import-Module "$modulePath\AutotaskAPI.psm1" -Force + +# Authenticate +Write-Host "[2] Authenticating to AutoTask API..." -ForegroundColor Yellow +$credFile = "C:\ProgramData\MXB\NABLE-CS66194_eric.harless_AutoTask_API_Credentials.Secure.xml" +$AutoTaskAPICreds = Import-Clixml $credFile + +$integrationCodeSecure = $AutoTaskAPICreds.IntegrationCode | ConvertTo-SecureString +$integrationcode = [Runtime.InteropServices.Marshal]::PtrToStringAuto( + [Runtime.InteropServices.Marshal]::SecureStringToBSTR($integrationCodeSecure) +) + +$secretSecure = $AutoTaskAPICreds.Secret | ConvertTo-SecureString +$credential = New-Object System.Management.Automation.PSCredential( + $AutoTaskAPICreds.Username, + $secretSecure +) + +Add-AutotaskAPIAuth -ApiIntegrationCode $integrationcode -credentials $credential +Write-Host " ✓ Authentication successful`n" -ForegroundColor Green + +# Check initial Queries table state +Write-Host "[3] Checking initial `$Script:Queries table..." -ForegroundColor Yellow +$atModule = Get-Module AutoTaskAPI +$queriesBefore = & $atModule { + $Script:Queries | Where-Object { $_.Post -eq 'Products' } | Select-Object Index, Name, Post +} +Write-Host " Products POST endpoints BEFORE Get-AutotaskAPIResource:" -ForegroundColor Gray +$queriesBefore | Format-Table -AutoSize | Out-String | Write-Host + +# Run Get-AutotaskAPIResource (this used to corrupt the table) +Write-Host "[4] Running Get-AutotaskAPIResource -Resource Products..." -ForegroundColor Yellow +$products = Get-AutotaskAPIResource -Resource Products -SimpleSearch 'isActive eq true' +Write-Host " ✓ Retrieved $($products.Count) products`n" -ForegroundColor Green + +# Check Queries table AFTER Get call (should be unchanged) +Write-Host "[5] Checking `$Script:Queries table AFTER Get..." -ForegroundColor Yellow +$queriesAfter = & $atModule { + $Script:Queries | Where-Object { $_.Post -eq 'Products' } | Select-Object Index, Name, Post +} +Write-Host " Products POST endpoints AFTER Get-AutotaskAPIResource:" -ForegroundColor Gray +$queriesAfter | Format-Table -AutoSize | Out-String | Write-Host + +# Compare before and after +$corruption = $false +for ($i = 0; $i -lt $queriesBefore.Count; $i++) { + if ($queriesBefore[$i].Name -ne $queriesAfter[$i].Name) { + Write-Host " ✗ CORRUPTION DETECTED!" -ForegroundColor Red + Write-Host " Before: $($queriesBefore[$i].Name)" -ForegroundColor Red + Write-Host " After: $($queriesAfter[$i].Name)" -ForegroundColor Red + $corruption = $true + } +} + +if (-not $corruption) { + Write-Host " ✓ No corruption - table unchanged (FIX WORKING!)`n" -ForegroundColor Green +} else { + Write-Host " ✗ Table was corrupted (FIX FAILED!)`n" -ForegroundColor Red + exit 1 +} + +# Now test New-AutotaskAPIResource +Write-Host "[6] Testing New-AutotaskAPIResource..." -ForegroundColor Yellow + +# Query existing product to get valid billing code +$existingProduct = $products | Select-Object -First 1 +$billingCodeID = $existingProduct.productBillingCodeID + +$testProduct = @{ + name = "TEST-MODULE-FIX-$(Get-Random -Minimum 1000 -Maximum 9999)" + description = "Test product to verify module fix" + sku = "TEST-FIX-001" + isActive = $true + unitCost = 0.00 + unitPrice = 1.00 + billingType = 2 # Monthly + periodType = 2 # Monthly + priceCostMethod = 1 # Fixed price + isSerialized = $false + productBillingCodeID = $billingCodeID +} + +try { + Write-Host " Creating test product: $($testProduct.name)" -ForegroundColor Gray + $result = New-AutotaskAPIResource -Resource Products -Body $testProduct -ErrorAction Stop + + if ($result.itemId) { + Write-Host " ✓ Product created successfully - ID: $($result.itemId)" -ForegroundColor Green + Write-Host " Note: Test product left in system (Products don't support DELETE)`n" -ForegroundColor Gray + + Write-Host "==================================================================" -ForegroundColor Green + Write-Host "✓ ALL TESTS PASSED - MODULE FIX VERIFIED!" -ForegroundColor Green + Write-Host "==================================================================`n" -ForegroundColor Green + exit 0 + } else { + Write-Host " ✗ Product creation returned no itemId`n" -ForegroundColor Red + exit 1 + } +} catch { + Write-Host " ✗ Product creation FAILED: $($_.Exception.Message)" -ForegroundColor Red + if ($_.ErrorDetails.Message) { + Write-Host " Details: $($_.ErrorDetails.Message)" -ForegroundColor Yellow + } + Write-Host "`n==================================================================" -ForegroundColor Red + Write-Host "✗ TEST FAILED - MODULE FIX NOT WORKING" -ForegroundColor Red + Write-Host "==================================================================`n" -ForegroundColor Red + exit 1 +}