sync: Dataforth sync fixes, TestDataDB stability, and client scripts
Dataforth DOS: - TestDataDB: singleton DB connection fix (crash prevention), WAL mode, WinSW service config, backup script, uncaught exception handlers - Sync-FromNAS.ps1: Get-NASFileList temp file approach to avoid SSH stdout deadlock, *> $null output suppression, 8.3 filename filter for PUSH phase, backslash-escaped SCP paths, rename-to-.synced - import.js: INSERT OR REPLACE for re-tested devices - Full import run: 1,028,275 -> 1,632,793 records, indexes added - Deploy script for sync fixes to AD2 Client scripts (temp/): - BG Builders: Lesley account check, MFA phone update - Lonestar Electrical: Kyla/Russ Google Workspace setup, 2FA bypass - AD2 diagnostics and NAS connectivity tests PENDING: Investigate why newest test_date is Jan 19 despite daily tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
28
temp/ad2-diag.ps1
Normal file
28
temp/ad2-diag.ps1
Normal file
@@ -0,0 +1,28 @@
|
||||
# Diagnostic script for TestDataDB on AD2
|
||||
Write-Output "=== Node Process ==="
|
||||
Get-CimInstance Win32_Process -Filter "Name='node.exe'" | Select-Object ProcessId, CommandLine | Format-List
|
||||
|
||||
Write-Output "=== HTTP Test ==="
|
||||
try {
|
||||
$r = Invoke-WebRequest -Uri "http://localhost:3000/" -UseBasicParsing -TimeoutSec 10
|
||||
Write-Output "Root page status: $($r.StatusCode)"
|
||||
Write-Output "Content length: $($r.Content.Length)"
|
||||
Write-Output "First 200 chars: $($r.Content.Substring(0, [Math]::Min(200, $r.Content.Length)))"
|
||||
} catch {
|
||||
Write-Output "Root page ERROR: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
Write-Output "`n=== API Test ==="
|
||||
try {
|
||||
$r = Invoke-WebRequest -Uri "http://localhost:3000/api/stats" -UseBasicParsing -TimeoutSec 10
|
||||
Write-Output "API status: $($r.StatusCode)"
|
||||
Write-Output "First 200 chars: $($r.Content.Substring(0, [Math]::Min(200, $r.Content.Length)))"
|
||||
} catch {
|
||||
Write-Output "API ERROR: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
Write-Output "`n=== Service Log Files ==="
|
||||
Get-ChildItem "C:\Shares\testdatadb\logs\" -ErrorAction SilentlyContinue | Format-Table Name, Length, LastWriteTime
|
||||
|
||||
Write-Output "`n=== Recent Event Log ==="
|
||||
Get-EventLog -LogName Application -Newest 5 -Source "*node*" -ErrorAction SilentlyContinue | Format-List
|
||||
51
temp/bgb-lesley-check.ps1
Normal file
51
temp/bgb-lesley-check.ps1
Normal file
@@ -0,0 +1,51 @@
|
||||
# Check Lesley's email activity since disable
|
||||
$ErrorActionPreference = "Stop"
|
||||
$lesleyUPN = "lesley@bgbuildersllc.com"
|
||||
|
||||
Import-Module ExchangeOnlineManagement
|
||||
Connect-ExchangeOnline -UserPrincipalName "sysadmin@bgbuildersllc.com" -ShowBanner:$false
|
||||
|
||||
$startDate = (Get-Date).AddDays(-3)
|
||||
$endDate = Get-Date
|
||||
|
||||
Write-Output "=== MAILBOX STATUS ==="
|
||||
$mbx = Get-Mailbox -Identity $lesleyUPN
|
||||
$stats = Get-MailboxStatistics -Identity $lesleyUPN
|
||||
Write-Output "Type: $($mbx.RecipientTypeDetails)"
|
||||
Write-Output "LitigationHold: $($mbx.LitigationHoldEnabled)"
|
||||
Write-Output "ItemCount: $($stats.ItemCount)"
|
||||
Write-Output "TotalSize: $($stats.TotalItemSize)"
|
||||
|
||||
Write-Output "`n=== SENT MESSAGES (last 3 days) ==="
|
||||
$sent = Get-MessageTraceV2 -SenderAddress $lesleyUPN -StartDate $startDate -EndDate $endDate
|
||||
if ($sent) {
|
||||
$sent | Format-Table Received,RecipientAddress,Subject -AutoSize
|
||||
} else {
|
||||
Write-Output "None found"
|
||||
}
|
||||
|
||||
Write-Output "`n=== RECEIVED MESSAGES (last 3 days) ==="
|
||||
$recv = Get-MessageTraceV2 -RecipientAddress $lesleyUPN -StartDate $startDate -EndDate $endDate
|
||||
if ($recv) {
|
||||
$recv | Select-Object -First 20 | Format-Table Received,SenderAddress,Subject -AutoSize
|
||||
} else {
|
||||
Write-Output "None found"
|
||||
}
|
||||
|
||||
Write-Output "`n=== INBOX RULES ==="
|
||||
$rules = Get-InboxRule -Mailbox $lesleyUPN
|
||||
if ($rules) {
|
||||
$rules | Format-Table Name,Enabled,Description -AutoSize
|
||||
} else {
|
||||
Write-Output "No inbox rules"
|
||||
}
|
||||
|
||||
Write-Output "`n=== FORWARDING CONFIG ==="
|
||||
Write-Output "ForwardingAddress: $($mbx.ForwardingAddress)"
|
||||
Write-Output "ForwardingSmtpAddress: $($mbx.ForwardingSmtpAddress)"
|
||||
Write-Output "DeliverToMailboxAndForward: $($mbx.DeliverToMailboxAndForward)"
|
||||
|
||||
Write-Output "`n=== FOLDER ITEM COUNTS ==="
|
||||
Get-MailboxFolderStatistics -Identity $lesleyUPN | Where-Object { $_.ItemsInFolder -gt 0 } | Sort-Object ItemsInFolder -Descending | Select-Object -First 15 | Format-Table Name,FolderType,ItemsInFolder,FolderSize -AutoSize
|
||||
|
||||
Disconnect-ExchangeOnline -Confirm:$false
|
||||
52
temp/bgb-lesley-mfa-phone.ps1
Normal file
52
temp/bgb-lesley-mfa-phone.ps1
Normal file
@@ -0,0 +1,52 @@
|
||||
# Update MFA phone number for Lesley Roth @ BG Builders
|
||||
$ErrorActionPreference = "Stop"
|
||||
$lesleyUPN = "lesley@bgbuildersllc.com"
|
||||
$newPhone = "+1 4804954511"
|
||||
$tenantId = "ededa4fb-f6eb-4398-851d-5eb3e11fab27"
|
||||
|
||||
Import-Module Microsoft.Graph.Authentication
|
||||
Import-Module Microsoft.Graph.Users
|
||||
|
||||
Connect-MgGraph -TenantId $tenantId -Scopes 'UserAuthenticationMethod.ReadWrite.All','User.ReadWrite.All' -NoWelcome
|
||||
|
||||
Write-Output "=== Current Auth Methods for Lesley ==="
|
||||
$methods = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$lesleyUPN/authentication/phoneMethods"
|
||||
if ($methods.value.Count -gt 0) {
|
||||
foreach ($m in $methods.value) {
|
||||
Write-Output " ID: $($m.id) | Type: $($m.phoneType) | Number: $($m.phoneNumber)"
|
||||
}
|
||||
} else {
|
||||
Write-Output " No phone methods registered"
|
||||
}
|
||||
|
||||
Write-Output "`n=== Updating MFA Phone ==="
|
||||
# Phone method ID for mobile is always "3179e48a-750b-4051-897c-87b9720928f7"
|
||||
$mobileMethodId = "3179e48a-750b-4051-897c-87b9720928f7"
|
||||
|
||||
try {
|
||||
# Try to update existing mobile phone method
|
||||
Invoke-MgGraphRequest -Method PUT -Uri "https://graph.microsoft.com/v1.0/users/$lesleyUPN/authentication/phoneMethods/$mobileMethodId" -Body @{
|
||||
phoneNumber = $newPhone
|
||||
phoneType = "mobile"
|
||||
}
|
||||
Write-Output "[OK] Mobile phone updated to $newPhone"
|
||||
} catch {
|
||||
Write-Output "[INFO] PUT failed, trying POST to create new method..."
|
||||
try {
|
||||
Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/users/$lesleyUPN/authentication/phoneMethods" -Body @{
|
||||
phoneNumber = $newPhone
|
||||
phoneType = "mobile"
|
||||
}
|
||||
Write-Output "[OK] Mobile phone created: $newPhone"
|
||||
} catch {
|
||||
Write-Output "[ERROR] Failed: $_"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "`n=== Verify Updated Methods ==="
|
||||
$methods = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$lesleyUPN/authentication/phoneMethods"
|
||||
foreach ($m in $methods.value) {
|
||||
Write-Output " ID: $($m.id) | Type: $($m.phoneType) | Number: $($m.phoneNumber)"
|
||||
}
|
||||
|
||||
Disconnect-MgGraph
|
||||
26
temp/bgb-lesley-mfa-phone2.ps1
Normal file
26
temp/bgb-lesley-mfa-phone2.ps1
Normal file
@@ -0,0 +1,26 @@
|
||||
# Update MFA phone number for Lesley Roth @ BG Builders
|
||||
$ErrorActionPreference = "Stop"
|
||||
$lesleyUPN = "lesley@bgbuildersllc.com"
|
||||
$newPhone = "+1 4804954511"
|
||||
$tenantId = "ededa4fb-f6eb-4398-851d-5eb3e11fab27"
|
||||
|
||||
Import-Module Microsoft.Graph.Authentication
|
||||
Connect-MgGraph -TenantId $tenantId -Scopes 'UserAuthenticationMethod.ReadWrite.All' -NoWelcome
|
||||
|
||||
$mobileMethodId = "3179e48a-750b-4051-897c-87b9720928f7"
|
||||
|
||||
Write-Output "Current: +1 4802299138"
|
||||
Write-Output "Changing to: $newPhone"
|
||||
|
||||
$body = @{ phoneNumber = $newPhone; phoneType = "mobile" } | ConvertTo-Json
|
||||
Invoke-MgGraphRequest -Method PUT -Uri "https://graph.microsoft.com/v1.0/users/$lesleyUPN/authentication/phoneMethods/$mobileMethodId" -Body $body -ContentType "application/json"
|
||||
|
||||
Write-Output "[OK] Phone updated"
|
||||
|
||||
# Verify
|
||||
$methods = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$lesleyUPN/authentication/phoneMethods"
|
||||
foreach ($m in $methods.value) {
|
||||
Write-Output "Verified: $($m.phoneType) = $($m.phoneNumber)"
|
||||
}
|
||||
|
||||
Disconnect-MgGraph
|
||||
52
temp/lonestar-kyla-2fa-fix.py
Normal file
52
temp/lonestar-kyla-2fa-fix.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""Generate backup codes for office@lonestarelectrical.net so Kyla can bypass 2FA enrollment block"""
|
||||
from google.oauth2 import service_account
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
SCOPES = [
|
||||
'https://www.googleapis.com/auth/admin.directory.user',
|
||||
'https://www.googleapis.com/auth/admin.directory.user.security',
|
||||
]
|
||||
|
||||
creds = service_account.Credentials.from_service_account_file(
|
||||
'temp/acg-msp-access-8f72339997e5.json', scopes=SCOPES
|
||||
)
|
||||
delegated = creds.with_subject('sysadmin@lonestarelectrical.net')
|
||||
service = build('admin', 'directory_v1', credentials=delegated)
|
||||
|
||||
user_email = 'office@lonestarelectrical.net'
|
||||
|
||||
# Check current 2SV status
|
||||
print(f"=== {user_email} 2SV Status ===")
|
||||
user = service.users().get(userKey=user_email).execute()
|
||||
print(f"2SV Enrolled: {user.get('isEnrolledIn2Sv', False)}")
|
||||
print(f"2SV Enforced: {user.get('isEnforcedIn2Sv', False)}")
|
||||
|
||||
# Generate backup verification codes
|
||||
print(f"\n=== Generating Backup Codes ===")
|
||||
try:
|
||||
codes = service.verificationCodes().generate(userKey=user_email).execute()
|
||||
print("[OK] Backup codes generated")
|
||||
except Exception as e:
|
||||
print(f"[INFO] Generate returned: {e}")
|
||||
|
||||
# List the codes
|
||||
try:
|
||||
result = service.verificationCodes().list(userKey=user_email).execute()
|
||||
backup_codes = result.get('items', [])
|
||||
if backup_codes:
|
||||
print(f"\nBackup codes for Kyla to use at login:")
|
||||
for code in backup_codes:
|
||||
status = code.get('etag', '')
|
||||
print(f" {code.get('verificationCode', 'N/A')}")
|
||||
print(f"\nInstructions for Kyla:")
|
||||
print(f" 1. Go to https://accounts.google.com")
|
||||
print(f" 2. Enter email: {user_email}")
|
||||
print(f" 3. Enter the temp password we set")
|
||||
print(f" 4. When prompted for 2FA, click 'Try another way'")
|
||||
print(f" 5. Select 'Enter a backup code'")
|
||||
print(f" 6. Use one of the codes above")
|
||||
print(f" 7. Once logged in, go to Security > 2-Step Verification to set up her phone")
|
||||
else:
|
||||
print("[WARNING] No codes returned")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Could not list codes: {e}")
|
||||
60
temp/lonestar-kyla-reset.py
Normal file
60
temp/lonestar-kyla-reset.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Reset password for office@lonestarelectrical.net so Kyla can login and set up MFA"""
|
||||
import secrets
|
||||
import string
|
||||
from google.oauth2 import service_account
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
SCOPES = [
|
||||
'https://www.googleapis.com/auth/admin.directory.user',
|
||||
'https://www.googleapis.com/auth/admin.directory.user.security',
|
||||
]
|
||||
|
||||
creds = service_account.Credentials.from_service_account_file(
|
||||
'temp/acg-msp-access-8f72339997e5.json', scopes=SCOPES
|
||||
)
|
||||
delegated = creds.with_subject('sysadmin@lonestarelectrical.net')
|
||||
service = build('admin', 'directory_v1', credentials=delegated)
|
||||
|
||||
user_email = 'office@lonestarelectrical.net'
|
||||
|
||||
# Check current user status
|
||||
print(f"=== Checking {user_email} ===")
|
||||
try:
|
||||
user = service.users().get(userKey=user_email).execute()
|
||||
print(f"Name: {user.get('name', {}).get('fullName', 'N/A')}")
|
||||
print(f"Suspended: {user.get('suspended', 'N/A')}")
|
||||
print(f"Archived: {user.get('archived', 'N/A')}")
|
||||
print(f"2FA Enrolled: {user.get('isEnrolledIn2Sv', 'N/A')}")
|
||||
print(f"2FA Enforced: {user.get('isEnforcedIn2Sv', 'N/A')}")
|
||||
print(f"Last Login: {user.get('lastLoginTime', 'N/A')}")
|
||||
print(f"Creation: {user.get('creationTime', 'N/A')}")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Could not get user: {e}")
|
||||
exit(1)
|
||||
|
||||
# Generate a temp password
|
||||
alphabet = string.ascii_letters + string.digits + "!@#$"
|
||||
temp_pass = ''.join(secrets.choice(alphabet) for _ in range(16))
|
||||
|
||||
# Reset password, require change on next login
|
||||
print(f"\n=== Resetting password ===")
|
||||
try:
|
||||
service.users().update(
|
||||
userKey=user_email,
|
||||
body={
|
||||
'password': temp_pass,
|
||||
'changePasswordAtNextLogin': True,
|
||||
'suspended': False,
|
||||
}
|
||||
).execute()
|
||||
print(f"[OK] Password reset successful")
|
||||
print(f"[OK] Account unsuspended (if it was)")
|
||||
print(f"[OK] Must change password on first login")
|
||||
print(f"\nTemporary password: {temp_pass}")
|
||||
print(f"\nGive Kyla:")
|
||||
print(f" Email: {user_email}")
|
||||
print(f" Password: {temp_pass}")
|
||||
print(f" URL: https://accounts.google.com")
|
||||
print(f" She will be prompted to change password and set up MFA")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Password reset failed: {e}")
|
||||
25
temp/lonestar-kyla-reset2.py
Normal file
25
temp/lonestar-kyla-reset2.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""Reset password for office@lonestarelectrical.net - attempt 2, no force change"""
|
||||
from google.oauth2 import service_account
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
SCOPES = ['https://www.googleapis.com/auth/admin.directory.user']
|
||||
|
||||
creds = service_account.Credentials.from_service_account_file(
|
||||
'temp/acg-msp-access-8f72339997e5.json', scopes=SCOPES
|
||||
)
|
||||
delegated = creds.with_subject('sysadmin@lonestarelectrical.net')
|
||||
service = build('admin', 'directory_v1', credentials=delegated)
|
||||
|
||||
user_email = 'office@lonestarelectrical.net'
|
||||
new_pass = 'LoneStar2026!!'
|
||||
|
||||
service.users().update(
|
||||
userKey=user_email,
|
||||
body={
|
||||
'password': new_pass,
|
||||
'changePasswordAtNextLogin': False,
|
||||
}
|
||||
).execute()
|
||||
|
||||
print(f"[OK] Password reset for {user_email}")
|
||||
print(f"Password: {new_pass}")
|
||||
41
temp/lonestar-russ-setup.py
Normal file
41
temp/lonestar-russ-setup.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""Reset password and generate backup codes for russ@lonestarelectrical.net"""
|
||||
from google.oauth2 import service_account
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
SCOPES = [
|
||||
'https://www.googleapis.com/auth/admin.directory.user',
|
||||
'https://www.googleapis.com/auth/admin.directory.user.security',
|
||||
]
|
||||
|
||||
creds = service_account.Credentials.from_service_account_file(
|
||||
'temp/acg-msp-access-8f72339997e5.json', scopes=SCOPES
|
||||
)
|
||||
delegated = creds.with_subject('sysadmin@lonestarelectrical.net')
|
||||
service = build('admin', 'directory_v1', credentials=delegated)
|
||||
|
||||
user_email = 'russ@lonestarelectrical.net'
|
||||
|
||||
# Check user
|
||||
print(f"=== {user_email} ===")
|
||||
user = service.users().get(userKey=user_email).execute()
|
||||
print(f"Name: {user.get('name', {}).get('fullName', 'N/A')}")
|
||||
print(f"2SV Enrolled: {user.get('isEnrolledIn2Sv', False)}")
|
||||
print(f"2SV Enforced: {user.get('isEnforcedIn2Sv', False)}")
|
||||
print(f"Last Login: {user.get('lastLoginTime', 'N/A')}")
|
||||
|
||||
# Reset password
|
||||
new_pass = 'LoneStar2026!!'
|
||||
service.users().update(
|
||||
userKey=user_email,
|
||||
body={'password': new_pass, 'changePasswordAtNextLogin': False, 'suspended': False}
|
||||
).execute()
|
||||
print(f"\n[OK] Password reset: {new_pass}")
|
||||
|
||||
# Generate backup codes
|
||||
service.verificationCodes().generate(userKey=user_email).execute()
|
||||
result = service.verificationCodes().list(userKey=user_email).execute()
|
||||
codes = result.get('items', [])
|
||||
if codes:
|
||||
print(f"\nBackup codes:")
|
||||
for c in codes:
|
||||
print(f" {c.get('verificationCode')}")
|
||||
19
temp/test-ad2-web.ps1
Normal file
19
temp/test-ad2-web.ps1
Normal file
@@ -0,0 +1,19 @@
|
||||
$SshExe = 'C:\Windows\System32\OpenSSH\ssh.exe'
|
||||
$SshTarget = 'INTRANET\sysadmin@192.168.0.6'
|
||||
|
||||
# Create a test script on AD2
|
||||
$testScript = @'
|
||||
try {
|
||||
$r = Invoke-WebRequest -Uri "http://localhost:3000/api/stats" -UseBasicParsing -TimeoutSec 5
|
||||
Write-Output "STATUS: $($r.StatusCode)"
|
||||
Write-Output "CONTENT: $($r.Content.Substring(0, [Math]::Min(200, $r.Content.Length)))"
|
||||
} catch {
|
||||
Write-Output "ERROR: $($_.Exception.Message)"
|
||||
}
|
||||
'@
|
||||
|
||||
# Write test script to AD2
|
||||
$testScript | & $SshExe $SshTarget 'powershell -Command "Set-Content -Path C:\Shares\testdatadb\test-web.ps1 -Value (Get-Content -Raw -Path -)"'
|
||||
|
||||
# Actually, simpler - just run inline
|
||||
& $SshExe $SshTarget 'powershell -NoProfile -ExecutionPolicy Bypass -Command "try { $r = Invoke-WebRequest -Uri http://localhost:3000/ -UseBasicParsing -TimeoutSec 5; Write-Output STATUS:$($r.StatusCode) } catch { Write-Output ERROR:$($_.Exception.Message) }"'
|
||||
3
temp/test-minimal.ps1
Normal file
3
temp/test-minimal.ps1
Normal file
@@ -0,0 +1,3 @@
|
||||
# Minimal test - just echo to NAS
|
||||
$r = & "C:\Program Files\OpenSSH\ssh.exe" -i C:\Users\sysadmin\.ssh\id_ed25519 -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new root@192.168.0.9 "echo MINIMAL_TEST_OK" 2>&1
|
||||
Write-Host "Result: $r"
|
||||
48
temp/test-nas-from-ad2.ps1
Normal file
48
temp/test-nas-from-ad2.ps1
Normal file
@@ -0,0 +1,48 @@
|
||||
# Test script to run ON AD2 - diagnoses NAS SSH hang issue
|
||||
$SSH = "C:\Program Files\OpenSSH\ssh.exe"
|
||||
$SSH_KEY = "C:\Users\sysadmin\.ssh\id_ed25519"
|
||||
$NAS_USER = "root"
|
||||
$NAS_IP = "192.168.0.9"
|
||||
|
||||
Write-Host "=== Step 1: Kill any hung SSH processes ==="
|
||||
Get-Process ssh -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
Write-Host " Killing SSH PID $($_.Id)"
|
||||
Stop-Process -Id $_.Id -Force
|
||||
}
|
||||
Get-Process powershell -ErrorAction SilentlyContinue | Where-Object { $_.Id -ne $PID } | ForEach-Object {
|
||||
Write-Host " Other PowerShell PID $($_.Id) - CommandLine: $($_.CommandLine)"
|
||||
}
|
||||
|
||||
Write-Host "`n=== Step 2: Basic SSH echo test ==="
|
||||
$t1 = Get-Date
|
||||
$r1 = & $SSH -i $SSH_KEY -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${NAS_USER}@${NAS_IP}" "echo NAS_OK" 2>&1
|
||||
$d1 = (Get-Date) - $t1
|
||||
Write-Host " Result: $r1 (took $($d1.TotalSeconds)s)"
|
||||
|
||||
Write-Host "`n=== Step 3: find with temp file redirect (the actual fix) ==="
|
||||
$t2 = Get-Date
|
||||
Write-Host " Running find with output to /tmp/test-list.txt..."
|
||||
# This is exactly what Get-NASFileList does - output goes to file on NAS, stdout discarded
|
||||
& $SSH -i $SSH_KEY -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${NAS_USER}@${NAS_IP}" "find /data/test/TS-*/LOGS -name '*.DAT' -type f -mmin -1440 > /tmp/test-list.txt 2>/dev/null" *> $null
|
||||
$d2 = (Get-Date) - $t2
|
||||
Write-Host " SSH returned in $($d2.TotalSeconds)s"
|
||||
|
||||
Write-Host "`n=== Step 4: Count files found ==="
|
||||
$r3 = & $SSH -i $SSH_KEY -o BatchMode=yes -o ConnectTimeout=10 "${NAS_USER}@${NAS_IP}" "wc -l /tmp/test-list.txt; head -3 /tmp/test-list.txt" 2>&1
|
||||
foreach ($line in $r3) { Write-Host " $line" }
|
||||
|
||||
Write-Host "`n=== Step 5: Pull file list via SCP ==="
|
||||
$localTemp = "$env:TEMP\test-nas-filelist.txt"
|
||||
& "C:\Program Files\OpenSSH\scp.exe" -O -i $SSH_KEY -o StrictHostKeyChecking=accept-new "${NAS_USER}@${NAS_IP}:/tmp/test-list.txt" "$localTemp" *> $null
|
||||
if (Test-Path $localTemp) {
|
||||
$lines = Get-Content $localTemp | Where-Object { $_.Trim() -ne '' }
|
||||
Write-Host " Downloaded $($lines.Count) file paths"
|
||||
Remove-Item $localTemp -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
Write-Host " ERROR: SCP failed to download file"
|
||||
}
|
||||
|
||||
Write-Host "`n=== Step 6: Cleanup ==="
|
||||
& $SSH -i $SSH_KEY -o BatchMode=yes -o ConnectTimeout=10 "${NAS_USER}@${NAS_IP}" "rm -f /tmp/test-list.txt" 2>&1 | Out-Null
|
||||
|
||||
Write-Host "`n=== DONE ==="
|
||||
32
temp/test-nas-v2.ps1
Normal file
32
temp/test-nas-v2.ps1
Normal file
@@ -0,0 +1,32 @@
|
||||
# Run ON AD2: Tests NAS SSH operations
|
||||
# Deploy: scp test-nas-v2.ps1 AD2:C:\Shares\testdatadb\
|
||||
# Run: powershell -NoProfile -ExecutionPolicy Bypass -File C:\Shares\testdatadb\test-nas-v2.ps1
|
||||
$SSH = "C:\Program Files\OpenSSH\ssh.exe"
|
||||
$SSH_KEY = "C:\Users\sysadmin\.ssh\id_ed25519"
|
||||
|
||||
Write-Host "=== Test A: echo ==="
|
||||
$t = Get-Date
|
||||
$r = & $SSH -i $SSH_KEY -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new root@192.168.0.9 "echo NAS_OK" 2>&1
|
||||
Write-Host " Result: $r ($(((Get-Date)-$t).TotalSeconds)s)"
|
||||
|
||||
Write-Host "=== Test B: ls data dir ==="
|
||||
$t = Get-Date
|
||||
$r = & $SSH -i $SSH_KEY -o BatchMode=yes -o ConnectTimeout=10 root@192.168.0.9 "ls /data/test/ | head -5" 2>&1
|
||||
foreach ($l in $r) { Write-Host " $l" }
|
||||
Write-Host " ($(((Get-Date)-$t).TotalSeconds)s)"
|
||||
|
||||
Write-Host "=== Test C: find with wc -l only ==="
|
||||
$t = Get-Date
|
||||
$r = & $SSH -i $SSH_KEY -o BatchMode=yes -o ConnectTimeout=10 root@192.168.0.9 "find /data/test/TS-*/LOGS -name '*.DAT' -type f -mmin -1440 2>/dev/null | wc -l" 2>&1
|
||||
Write-Host " Count: $r ($(((Get-Date)-$t).TotalSeconds)s)"
|
||||
|
||||
Write-Host "=== Test D: find to temp file ==="
|
||||
$t = Get-Date
|
||||
& $SSH -i $SSH_KEY -o BatchMode=yes -o ConnectTimeout=10 root@192.168.0.9 "find /data/test/TS-*/LOGS -name '*.DAT' -type f -mmin -1440 > /tmp/test-list.txt 2>/dev/null" *> $null
|
||||
Write-Host " ($(((Get-Date)-$t).TotalSeconds)s)"
|
||||
|
||||
Write-Host "=== Test E: check temp file ==="
|
||||
$r = & $SSH -i $SSH_KEY -o BatchMode=yes -o ConnectTimeout=10 root@192.168.0.9 "wc -l /tmp/test-list.txt; rm -f /tmp/test-list.txt" 2>&1
|
||||
Write-Host " $r"
|
||||
|
||||
Write-Host "=== ALL DONE ==="
|
||||
15
temp/testdatadb.xml
Normal file
15
temp/testdatadb.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<service>
|
||||
<id>testdatadb</id>
|
||||
<name>TestDataDB</name>
|
||||
<description>Dataforth Test Data Database Server</description>
|
||||
<executable>C:\Program Files\nodejs\node.exe</executable>
|
||||
<argument>C:\Shares\testdatadb\server.js</argument>
|
||||
<logpath>C:\Shares\testdatadb\logs</logpath>
|
||||
<logmode>rotate</logmode>
|
||||
<stoptimeout>15sec</stoptimeout>
|
||||
<workingdirectory>C:\Shares\testdatadb</workingdirectory>
|
||||
<onfailure action="restart" delay="5 sec"/>
|
||||
<onfailure action="restart" delay="10 sec"/>
|
||||
<onfailure action="restart" delay="30 sec"/>
|
||||
<resetfailure>1 hour</resetfailure>
|
||||
</service>
|
||||
Reference in New Issue
Block a user