# Dataforth Test Datasheet Uploader (daily) $ErrorActionPreference = 'Stop' $prod = 'C:\ProgramData\dataforth-uploader' $logDir = Join-Path $prod 'logs' $uploadLogDir = Join-Path $prod 'upload-logs' $nodeExe = 'C:\Program Files\nodejs\node.exe' New-Item -ItemType Directory -Force -Path $logDir | Out-Null $stamp = Get-Date -Format 'yyyy-MM-dd_HH-mm-ss' $log = Join-Path $logDir "pipeline-$stamp.log" $graphCreds = $null function Log([string]$m) { $line = "[$(Get-Date -Format o)] $m" Write-Host $line Add-Content -Path $log -Value $line -Encoding utf8 } function SendEmail([string]$Subject, [string]$Body) { if ($null -eq $graphCreds) { return } try { # Acquire Graph token $tokenBody = @{ grant_type = 'client_credentials' client_id = $graphCreds.clientId client_secret = $graphCreds.clientSecret scope = 'https://graph.microsoft.com/.default' } $tokenResp = Invoke-RestMethod ` -Method Post ` -Uri "https://login.microsoftonline.com/$($graphCreds.tenantId)/oauth2/v2.0/token" ` -Body $tokenBody $token = $tokenResp.access_token # Send mail via Graph $mailPayload = @{ message = @{ subject = $Subject body = @{ contentType = 'Text'; content = $Body } toRecipients = @(@{ emailAddress = @{ address = 'mike@azcomputerguru.com' } }) from = @{ emailAddress = @{ address = 'sysadmin@dataforth.com' } } } } | ConvertTo-Json -Depth 10 Invoke-RestMethod ` -Method Post ` -Uri 'https://graph.microsoft.com/v1.0/users/sysadmin@dataforth.com/sendMail' ` -Headers @{ Authorization = "Bearer $token"; 'Content-Type' = 'application/json' } ` -Body $mailPayload | Out-Null Log "Email sent: $Subject" } catch { Log "WARNING: Email send failed: $_" } } try { Log "=== pipeline start (pid=$PID) ===" # Load credentials $creds = Get-Content (Join-Path $prod 'credentials.json') -Raw | ConvertFrom-Json $env:CF_TOKEN_URL = $creds.CF_TOKEN_URL $env:CF_API_BASE = $creds.CF_API_BASE $env:CF_CLIENT_ID = $creds.CF_CLIENT_ID $env:CF_CLIENT_SECRET = $creds.CF_CLIENT_SECRET $env:CF_SCOPE = $creds.CF_SCOPE if ($creds.GRAPH_TENANT_ID -and $creds.GRAPH_CLIENT_ID -and $creds.GRAPH_CLIENT_SECRET) { $graphCreds = [PSCustomObject]@{ tenantId = $creds.GRAPH_TENANT_ID clientId = $creds.GRAPH_CLIENT_ID clientSecret = $creds.GRAPH_CLIENT_SECRET } Log "Graph credentials loaded (app $($creds.GRAPH_CLIENT_ID))" } else { Log "WARNING: GRAPH_TENANT_ID/CLIENT_ID/CLIENT_SECRET not in credentials.json — email notifications disabled" } # [1] DFWDS process Log '[1] dfwds-process.js' $dfwdsJs = Join-Path $prod 'dfwds-process.js' $out = & $nodeExe $dfwdsJs 2>&1 $out | ForEach-Object { Log $_ } # [2] Enumerate For_Web Log '[2] enumerate For_Web' $delta = Join-Path $prod 'delta_for_web_all.txt' Get-ChildItem 'C:\Shares\webshare\For_Web' -File -Filter *.TXT | ForEach-Object { $sn = [System.IO.Path]::GetFileNameWithoutExtension($_.Name) "$sn|$($_.FullName)|$($_.Length)|$($_.LastWriteTime.ToString('o'))" } | Set-Content -Path $delta -Encoding ASCII $count = (Get-Content $delta).Count Log " enumerated $count files" # [3] Upload via Node Log '[3] upload-delta.js' $uploadJs = Join-Path $prod 'upload-delta.js' $out = & $nodeExe $uploadJs --delta $delta --batch 100 2>&1 $out | ForEach-Object { Log $_ } # [4] Daily summary email Log '[4] sending daily summary email' if ($graphCreds) { try { # Parse totals from most recent upload log; default to zeros if not found $totals = [PSCustomObject]@{ received=0; created=0; updated=0; unchanged=0; errors=0 } $latestUploadLog = Get-ChildItem $uploadLogDir -Filter '*.log' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1 if ($latestUploadLog) { $totalsLine = Get-Content $latestUploadLog.FullName -ErrorAction SilentlyContinue | Where-Object { $_ -match '^# totals ' } | Select-Object -Last 1 if ($totalsLine) { $totals = ($totalsLine -replace '^# totals\s*','') | ConvertFrom-Json } } $status = if ($totals.errors -eq 0) { 'OK' } else { 'FAIL' } $dateStr = Get-Date -Format 'yyyy-MM-dd' $summaryBody = @" Dataforth test datasheet daily pipeline completed on $env:COMPUTERNAME. Time: $(Get-Date -Format o) Files processed : $($totals.received) Created (new on website) : $($totals.created) Updated (refreshed) : $($totals.updated) Unchanged : $($totals.unchanged) Errors : $($totals.errors) Log: $($latestUploadLog.FullName) "@ SendEmail "[TestDataDB] Daily pipeline $status — $dateStr" $summaryBody } catch { Log "WARNING: Summary email failed: $_" } } else { Log ' Graph credentials not configured — skipping summary email' } Log '=== pipeline end (OK) ===' } catch { Log "FATAL: $_" Log "StackTrace: $($_.ScriptStackTrace)" $date = Get-Date -Format 'yyyy-MM-dd' $host_ = $env:COMPUTERNAME $errBody = @" Host: $host_ Time: $(Get-Date -Format o) Date: $date FATAL ERROR ----------- $_ Stack Trace ----------- $($_.ScriptStackTrace) Log file: $log "@ SendEmail "[TestDataDB] PIPELINE FAILURE — $date" $errBody throw } finally { # Retention: keep 60 days of pipeline logs Get-ChildItem $logDir -Filter 'pipeline-*.log' -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-60) } | Remove-Item -Force -ErrorAction SilentlyContinue }