sync: Multi-project updates - SolverBot, GuruRMM, Dataforth

SolverBot:
- Inject active project path into agent system prompts so agents
  know which directory to scope file operations to

GuruRMM:
- Bump agent version to 0.6.0
- Add serde aliases for PowerShell/ClaudeTask command types
- Add typed CommandType enum on server for proper serialization
- Support claude_task command type in send_command API

Dataforth:
- Fix SCP space-escaping in Sync-FromNAS.ps1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 16:16:18 -07:00
parent 6d3582d5dc
commit 8b6f0bcc96
37 changed files with 1544 additions and 15 deletions

View File

@@ -68,9 +68,9 @@ function Copy-FromNAS {
New-Item -ItemType Directory -Path $localDir -Force | Out-Null New-Item -ItemType Directory -Path $localDir -Force | Out-Null
} }
# FIX: Use -i for key auth, remove embedded quotes around remote path # Escape spaces in remote path for SCP (unescaped spaces cause "ambiguous target")
# DOS 8.3 filenames have no spaces - embedded quotes caused "ambiguous target" $escapedRemote = $RemotePath -replace ' ', '\ '
$result = & $SCP -O -i $SSH_KEY -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile="C:\Shares\test\scripts\.ssh\known_hosts" "${NAS_USER}@${NAS_IP}:${RemotePath}" "$LocalPath" 2>&1 $result = & $SCP -O -i $SSH_KEY -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile="C:\Shares\test\scripts\.ssh\known_hosts" "${NAS_USER}@${NAS_IP}:${escapedRemote}" "$LocalPath" 2>&1
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
$errorMsg = $result | Out-String $errorMsg = $result | Out-String
Write-Log " SCP PULL ERROR (exit $LASTEXITCODE): $errorMsg" Write-Log " SCP PULL ERROR (exit $LASTEXITCODE): $errorMsg"
@@ -93,9 +93,9 @@ function Copy-ToNAS {
$remoteDir = ($RemotePath -replace '[^/]+$', '').TrimEnd('/') $remoteDir = ($RemotePath -replace '[^/]+$', '').TrimEnd('/')
Invoke-NASCommand "mkdir -p '$remoteDir'" | Out-Null Invoke-NASCommand "mkdir -p '$remoteDir'" | Out-Null
# FIX: Use -i for key auth, remove embedded quotes around remote path # Escape spaces in remote path for SCP (unescaped spaces cause "ambiguous target")
# DOS 8.3 filenames have no spaces - embedded quotes caused "ambiguous target" $escapedRemote = $RemotePath -replace ' ', '\ '
$result = & $SCP -O -i $SSH_KEY -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile="C:\Shares\test\scripts\.ssh\known_hosts" "$LocalPath" "${NAS_USER}@${NAS_IP}:${RemotePath}" 2>&1 $result = & $SCP -O -i $SSH_KEY -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile="C:\Shares\test\scripts\.ssh\known_hosts" "$LocalPath" "${NAS_USER}@${NAS_IP}:${escapedRemote}" 2>&1
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
$errorMsg = $result | Out-String $errorMsg = $result | Out-String
Write-Log " SCP PUSH ERROR (exit $LASTEXITCODE): $errorMsg" Write-Log " SCP PUSH ERROR (exit $LASTEXITCODE): $errorMsg"

View File

@@ -0,0 +1,606 @@
# GuruRMM - Complete Reference Documentation
**Project:** GuruRMM - Remote Monitoring and Management Platform
**Version:** Server 0.2.0 / Agent 0.3.5 (deployed as 0.5.1)
**Last Updated:** 2026-02-17
---
## Table of Contents
1. [Project Overview](#project-overview)
2. [Architecture](#architecture)
3. [API Endpoints](#api-endpoints)
4. [WebSocket Protocol](#websocket-protocol)
5. [Command Execution](#command-execution)
6. [Claude Code Integration](#claude-code-integration)
7. [Agent Configuration](#agent-configuration)
8. [Deployed Agents](#deployed-agents)
9. [Database](#database)
10. [Authentication](#authentication)
11. [Auto-Update System](#auto-update-system)
12. [Known Issues](#known-issues)
13. [Development](#development)
14. [File Structure](#file-structure)
---
## Project Overview
GuruRMM is a Remote Monitoring and Management (RMM) platform built entirely in Rust. It provides real-time agent monitoring, remote command execution, system metrics collection, and service watchdog capabilities for managed IT environments.
### Technology Stack
| Component | Technology | Version |
|------------|-----------------------------------------|---------|
| Server | Rust (Axum 0.7, SQLx 0.8, PostgreSQL) | 0.2.0 |
| Agent | Rust (cross-platform, native service) | 0.3.5 (deployed as 0.5.1) |
| Dashboard | React + TypeScript + Vite | -- |
| Real-time | WebSocket (tokio-tungstenite) | -- |
| Database | PostgreSQL | -- |
---
## Architecture
### Server
- **Internal Address:** 172.16.3.30:3001
- **Production URL:** https://rmm-api.azcomputerguru.com
- **WebSocket Endpoint:** wss://rmm-api.azcomputerguru.com/ws
- **Database:** PostgreSQL (same server)
- **Service:** systemd unit `gururmm-server`
- **Source:** `D:\ClaudeTools\projects\msp-tools\guru-rmm\server\`
### Agent
- **Windows Service Name:** GuruRMM (uses native-service feature)
- **Legacy Mode:** NSSM wrapper for Windows 7 / Server 2008 R2
- **Config Path:** `C:\ProgramData\GuruRMM\agent.toml`
- **Binary Path:** `C:\Program Files\GuruRMM\gururmm-agent.exe`
- **Source:** `D:\ClaudeTools\projects\msp-tools\guru-rmm\agent\`
### Communication Model
```
+-------------------+ WebSocket (persistent, bidirectional) +-------------------+
| GuruRMM Agent | <-----------------------------------------------> | GuruRMM Server |
| (Windows/Linux) | | (Axum + Tokio) |
+-------------------+ +-------------------+
|
| REST API (JWT)
v
+-------------------+
| Dashboard |
| (React + TS) |
+-------------------+
```
- **Primary:** WebSocket -- persistent bidirectional connection between agent and server
- **Legacy Fallback:** REST heartbeat polling -- [WARNING] NOT FULLY IMPLEMENTED
- **Auth:** API key sent in initial WebSocket authentication message
- **Site-Based Auth:** WORD-WORD-NUMBER format site codes combined with device_id
---
## API Endpoints
### Authentication
| Method | Path | Description | Auth Required |
|--------|--------------------|----------------------------|---------------|
| POST | /api/auth/login | User login (email/password -> JWT) | No |
| POST | /api/auth/register | User registration | No (disabled) |
| GET | /api/auth/me | Get current user info | Yes |
### Clients
| Method | Path | Description | Auth Required |
|--------|-------------------------|-------------------------|---------------|
| GET | /api/clients | List all clients | Yes |
| POST | /api/clients | Create client | Yes |
| GET | /api/clients/:id | Get client by ID | Yes |
| PUT | /api/clients/:id | Update client | Yes |
| DELETE | /api/clients/:id | Delete client | Yes |
| GET | /api/clients/:id/sites | List client's sites | Yes |
### Sites
| Method | Path | Description | Auth Required |
|--------|--------------------------------|--------------------------|---------------|
| GET | /api/sites | List all sites | Yes |
| POST | /api/sites | Create site | Yes |
| GET | /api/sites/:id | Get site by ID | Yes |
| PUT | /api/sites/:id | Update site | Yes |
| DELETE | /api/sites/:id | Delete site | Yes |
| POST | /api/sites/:id/regenerate-key | Regenerate site API key | Yes |
### Agents
| Method | Path | Description | Auth Required |
|--------|--------------------------|--------------------------------------|---------------|
| GET | /api/agents | List all agents | Yes |
| POST | /api/agents | Register agent (authenticated) | Yes |
| GET | /api/agents/stats | Agent statistics | Yes |
| GET | /api/agents/unassigned | List unassigned agents | Yes |
| GET | /api/agents/:id | Get agent details | Yes |
| DELETE | /api/agents/:id | Delete agent | Yes |
| POST | /api/agents/:id/move | Move agent to different site | Yes |
| GET | /api/agents/:id/state | Get agent state (network, metrics) | Yes |
### Commands
| Method | Path | Description | Auth Required |
|--------|----------------------------|----------------------------|---------------|
| POST | /api/agents/:id/command | Send command to agent | Yes |
| GET | /api/commands | List recent commands | Yes |
| GET | /api/commands/:id | Get command status/result | Yes |
### Metrics
| Method | Path | Description | Auth Required |
|--------|----------------------------|---------------------------|---------------|
| GET | /api/agents/:id/metrics | Get agent metrics history | Yes |
| GET | /api/metrics/summary | Metrics summary | Yes |
### Legacy Agent Endpoints
These endpoints do **not** require JWT authentication. They are used by agents in legacy polling mode.
| Method | Path | Description | Auth Required |
|--------|-------------------------------|------------------------------|---------------|
| POST | /api/agent/register-legacy | Register with site code | No |
| POST | /api/agent/heartbeat | Agent heartbeat | No |
| POST | /api/agent/command-result | Submit command result | No |
[WARNING] Legacy heartbeat returns empty `pending_commands` -- not implemented (agents.rs line 334).
[WARNING] Legacy command-result endpoint does not store results (agents.rs lines 354-360).
### WebSocket
| Method | Path | Description | Auth Required |
|--------|------|------------------------|---------------------|
| GET | /ws | WebSocket upgrade | API key in auth msg |
---
## WebSocket Protocol
### Connection Flow
1. Client initiates WebSocket upgrade to `wss://rmm-api.azcomputerguru.com/ws`
2. Agent sends authentication message with API key and device info
3. Server validates API key (SHA256 hash match or site code lookup)
4. On success, server registers the WebSocket connection for the agent
5. Bidirectional message exchange begins
### Message Types
**Agent -> Server:**
- `Auth` -- Initial authentication payload (api_key, hostname, os_info, version)
- `Heartbeat` -- Periodic keepalive
- `MetricsReport` -- System metrics (CPU, memory, disk, network)
- `NetworkState` -- Network configuration snapshot (hash-based change detection)
- `CommandResult` -- Result of executed command (exit_code, stdout, stderr, duration)
- `WatchdogEvent` -- Service monitoring event
**Server -> Agent:**
- `AuthResponse` -- Success/failure of authentication
- `Command` -- Command to execute (CommandPayload)
- `Update` -- Auto-update instruction (download_url, checksum)
- `Ping` -- Keepalive ping
---
## Command Execution
### Command Types
| Type | Description | Shell Used |
|--------------|----------------------------------------------|---------------------------------------------|
| shell | System shell command | cmd.exe (Windows), /bin/sh (Unix) |
| powershell | PowerShell command | powershell -NoProfile -NonInteractive -Command |
| python | Python inline code | python -c |
| script | Custom interpreter | Configurable |
| claude_task | Claude Code task execution (special handler) | Claude Code CLI |
### Command Flow
```
1. Dashboard sends POST /api/agents/:id/command
Body: { command_type, command, timeout_seconds, elevated }
2. Server creates command record in database (status = pending)
3. If agent is connected via WebSocket:
-> Server sends command via WebSocket
-> Status updated to "running"
4. If agent is offline:
-> Command stays as "pending" (queued)
5. Agent receives command and executes it
6. Agent sends CommandResult back via WebSocket
-> { id, exit_code, stdout, stderr, duration_ms }
7. Server updates database with result
```
### Command States
| State | Description |
|-----------|------------------------------------------------|
| pending | Created, agent offline or not yet sent |
| running | Sent to agent via WebSocket, awaiting result |
| completed | Agent reported exit_code = 0 |
| failed | Agent reported exit_code != 0 |
### [BUG] Server-Agent Command Type Mismatch
This is a **critical** known bug that prevents all remote command execution.
**Root Cause:**
The server's `CommandPayload` serializes `command_type` as a plain JSON string:
```json
{"command_type": "powershell", "command": "Get-Process", ...}
```
The agent's `CommandPayload` expects `command_type` as a Rust enum (`CommandType::PowerShell`), which serde deserializes from an object or tagged format, not a bare string.
**Result:** Serde deserialization fails silently on the agent side. Commands are never executed. All commands remain in "running" state permanently because no `CommandResult` is ever sent back.
**Fix Required:** Either:
- Change the server to serialize `command_type` in the enum format the agent expects, OR
- Change the agent to accept plain string values for `command_type`
---
## Claude Code Integration
### Architecture
The agent includes a built-in Claude Code executor for running AI-assisted tasks.
- **Singleton:** Global `ClaudeExecutor` via `once_cell::Lazy`
- **Working Directory:** Restricted to `C:\Shares\test\` only
- **Rate Limit:** 10 tasks per hour (sliding window)
- **Max Concurrent:** 2 simultaneous tasks
- **Default Timeout:** 300 seconds (max 600)
- **Input Sanitization:** Blocks `& | ; $ ( ) < > \` \n \r`
### Claude Task Command Format
The server sends:
```json
{
"type": "command",
"payload": {
"id": "uuid",
"command_type": "claude_task",
"command": "task description",
"timeout_seconds": 300,
"elevated": false
}
}
```
[WARNING] This also suffers from the command type mismatch bug. The agent expects `command_type` to be an object for ClaudeTask:
```json
{
"claude_task": {
"task": "...",
"working_directory": "...",
"context_files": [...]
}
}
```
### Exit Codes
| Code | Meaning |
|------|------------------------------------------|
| 0 | Task completed successfully |
| 1 | Task failed |
| 124 | Task timed out |
| -1 | Executor error (rate limit, validation) |
---
## Agent Configuration
### agent.toml Format
```toml
[server]
url = "wss://rmm-api.azcomputerguru.com/ws"
api_key = "SITE-CODE-1234" # or grmm_xxxxx API key
[metrics]
interval_seconds = 60 # Range: 10-3600, default: 60
collect_cpu = true
collect_memory = true
collect_disk = true
collect_network = true
[watchdog]
enabled = true
check_interval_seconds = 30
[[watchdog.services]]
name = "ServiceName"
action = "restart"
max_restarts = 3
restart_cooldown_seconds = 60
```
### Hardcoded Intervals
These values are currently not configurable via `agent.toml`:
| Interval | Value | Notes |
|----------------------------|-------------|--------------------------------|
| Heartbeat | 30 seconds | |
| Network state check | 30 seconds | Uses hash-based change detection |
| Connection idle timeout | 90 seconds | |
| Auth timeout | 10 seconds | |
| Reconnect delay | 10 seconds | |
| Command execution timeout | 300 seconds | Configurable per command |
---
## Deployed Agents
| Hostname | Agent ID (prefix) | Version | OS | Status |
|-------------|--------------------|---------|-----------------------------|---------|
| ACG-M-L5090 | 97f63c3b-... | 0.5.1 | Windows 11 (26200) | online |
| AD2 | d28a1c90-... | 0.5.1 | Windows Server 2016 (14393) | online |
| gururmm | 8cd0440f-... | 0.5.1 | Ubuntu 22.04 | offline |
| SL-SERVER | 2585f6d5-... | 0.5.1 | unknown | offline |
| SL-SERVER | dff818e6-... | 0.5.1 | unknown | online |
---
## Database
### Connection Details
| Parameter | Value |
|-----------|------------------------------------|
| Host | 172.16.3.30 |
| Port | 5432 |
| Database | gururmm |
| User | gururmm |
| Password | 43617ebf7eb242e814ca9988cc4df5ad |
### Key Tables
| Table | Description |
|---------------------|------------------------------------------------|
| users | User accounts (JWT auth, Argon2id hashing) |
| clients | Client organizations |
| sites | Physical locations with API keys |
| agents | RMM agent instances |
| agent_state | Latest agent state (network, metrics snapshot) |
| agent_updates | Agent update tracking |
| alerts | System alerts |
| commands | Remote command execution log |
| metrics | Performance metrics time series |
| policies | Configuration policies |
| registration_tokens | Agent registration tokens |
| watchdog_events | Service monitoring events |
---
## Authentication
### API Authentication (JWT)
1. Send `POST /api/auth/login` with `{ email, password }`
2. Server validates credentials (Argon2id password hash)
3. Returns JWT token (24-hour expiry)
4. Include token in subsequent requests: `Authorization: Bearer <token>`
**Admin Credentials:**
| Field | Value |
|----------|------------------------------------|
| Email | claude-api@azcomputerguru.com |
| Password | ClaudeAPI2026!@# |
### Agent Authentication (API Key)
Two authentication modes:
1. **Direct API Key** -- Agent sends `grmm_xxxxx` format key, server matches against `api_key_hash` (SHA256) in agents table
2. **Site-Based** -- Agent sends site code (WORD-WORD-NUMBER format, e.g., `DARK-GROVE-7839`) combined with `device_id`, server looks up site and registers/matches agent
### SSO (Optional)
- **Provider:** Microsoft Entra ID
- **Client ID:** 18a15f5d-7ab8-46f4-8566-d7b5436b84b6
---
## Auto-Update System
### Update Flow
```
1. Agent connects via WebSocket and sends its version in the auth payload
2. Server checks if a newer version is available for the agent's OS/architecture
3. If update needed:
-> Server sends Update message with download_url and SHA256 checksum
4. Agent downloads the new binary from the download URL
5. Agent verifies the SHA256 checksum
6. Agent replaces its own binary and restarts
7. On reconnection, agent reports previous_version in auth payload
8. Server marks the update as completed
```
### Download Location
- **Server Path:** `/var/www/gururmm/downloads/`
- **Public URL:** `https://rmm-api.azcomputerguru.com/downloads/`
---
## Known Issues
### CRITICAL
| ID | Type | Description |
|----|------------|--------------------------------------------------------------------------------------------|
| 1 | [BUG] | Command type mismatch between server (String) and agent (Enum) -- commands never execute |
| 2 | [TODO] | Legacy heartbeat returns empty pending_commands (agents.rs line 334) |
| 3 | [TODO] | Legacy command-result endpoint does not store results (agents.rs lines 354-360) |
| 4 | [SECURITY] | CORS configured with AllowOrigin::Any -- should be restricted to known origins |
### MAJOR
| ID | Description |
|----|--------------------------------------------------------------------------------|
| 1 | No command timeout enforcement on server side |
| 2 | No retry logic for failed WebSocket sends |
| 3 | Database inconsistency: agent shows "online" but command sends fail silently |
| 4 | Missing database indexes on frequently queried columns |
| 5 | No rate limiting on command submissions |
### MINOR
| ID | Description |
|----|--------------------------------------------------------------------------|
| 1 | Hardcoded intervals (heartbeat, network check) not configurable |
| 2 | Watchdog events logged but not stored in database |
| 3 | No log rotation configured |
| 4 | Unicode characters in agent output (should use ASCII per coding guidelines) |
---
## Development
### Building
```bash
# Server
cd server && cargo build --release
# Agent (Windows, native service mode)
cd agent && cargo build --release
# Agent (Legacy mode for Windows 7 / Server 2008 R2)
cd agent && cargo build --release --features legacy --no-default-features
```
### Testing
```bash
cargo test # Run unit tests
cargo clippy # Run linter
cargo fmt --check # Check formatting
```
### Deploying the Server
```bash
# On gururmm server (172.16.3.30)
systemctl stop gururmm-server
cp target/release/gururmm-server /opt/gururmm/
systemctl start gururmm-server
journalctl -u gururmm-server -f
```
### Deploying the Agent
```cmd
REM On target Windows machine
sc stop GuruRMM
copy gururmm-agent.exe "C:\Program Files\GuruRMM\gururmm-agent.exe"
sc start GuruRMM
```
---
## File Structure
```
D:\ClaudeTools\projects\msp-tools\guru-rmm\
|
+-- agent/
| +-- src/
| | +-- main.rs # Entry point, CLI parsing, service install
| | +-- config.rs # TOML config loading and validation
| | +-- claude.rs # Claude Code executor (rate-limited singleton)
| | +-- service.rs # Windows service handler (native-service feature)
| | +-- device_id.rs # Hardware-based device ID generation
| | +-- transport/
| | | +-- mod.rs # Message types (AgentMessage, ServerMessage, CommandType enum)
| | | +-- websocket.rs # WebSocket client, reconnection, command execution
| | +-- metrics/
| | | +-- mod.rs # System metrics collection, network state hashing
| | +-- updater/
| | +-- mod.rs # Self-update logic (download, verify, replace)
| +-- deploy/ # Deployment configs per site
| +-- Cargo.toml # v0.3.5, features: native-service, legacy
|
+-- server/
| +-- src/
| | +-- main.rs # Axum server setup, router, middleware
| | +-- api/
| | | +-- mod.rs # Route definitions and grouping
| | | +-- agents.rs # Agent management + legacy polling endpoints
| | | +-- commands.rs # Command dispatch and status tracking
| | | +-- auth.rs # JWT login, registration, user info
| | | +-- clients.rs # Client CRUD operations
| | | +-- sites.rs # Site management and API key regeneration
| | | +-- metrics.rs # Metrics query endpoints
| | +-- ws/
| | | +-- mod.rs # WebSocket handler, ServerMessage types, CommandPayload (String type)
| | +-- db/
| | | +-- agents.rs # Agent database operations
| | | +-- commands.rs # Command database operations
| | +-- auth/
| | +-- mod.rs # JWT middleware and token validation
| +-- Cargo.toml # v0.2.0
|
+-- dashboard/ # React frontend (if present)
|
+-- docs/
+-- FEATURE_ROADMAP.md # Complete feature plan (654 lines)
+-- REMEDIATION_PLAN.md # Security and code review (1277 lines)
```
---
## Quick Reference
| Item | Value |
|--------------------|---------------------------------------------|
| Server URL | https://rmm-api.azcomputerguru.com |
| WebSocket URL | wss://rmm-api.azcomputerguru.com/ws |
| Internal Address | 172.16.3.30:3001 |
| Database | PostgreSQL @ 172.16.3.30:5432/gururmm |
| Service Name | gururmm-server (systemd) |
| Agent Service | GuruRMM (Windows SCM) |
| Agent Config | C:\ProgramData\GuruRMM\agent.toml |
| Agent Binary | C:\Program Files\GuruRMM\gururmm-agent.exe |
| Downloads | https://rmm-api.azcomputerguru.com/downloads/ |
| Admin Email | claude-api@azcomputerguru.com |
| SSO Client ID | 18a15f5d-7ab8-46f4-8566-d7b5436b84b6 |
---
*Document generated 2026-02-17. Source of truth for GuruRMM project reference.*

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "gururmm-agent" name = "gururmm-agent"
version = "0.3.5" version = "0.6.0"
edition = "2021" edition = "2021"
description = "GuruRMM Agent - Cross-platform RMM agent" description = "GuruRMM Agent - Cross-platform RMM agent"
authors = ["GuruRMM"] authors = ["GuruRMM"]

View File

@@ -199,6 +199,9 @@ pub enum CommandType {
Shell, Shell,
/// PowerShell command (Windows) /// PowerShell command (Windows)
/// Alias "powershell" for backwards compatibility with servers that send
/// the command type as a plain string instead of snake_case enum format.
#[serde(alias = "powershell")]
PowerShell, PowerShell,
/// Python script /// Python script
@@ -208,6 +211,7 @@ pub enum CommandType {
Script { interpreter: String }, Script { interpreter: String },
/// Claude Code task execution /// Claude Code task execution
#[serde(alias = "claude_task")]
ClaudeTask { ClaudeTask {
/// Task description for Claude Code /// Task description for Claude Code
task: String, task: String,

View File

@@ -0,0 +1,67 @@
import requests, json, time, sys
with open("/tmp/agent_b64.txt") as f:
b64 = f.read().strip()
print("Base64 length:", len(b64))
token_r = requests.post("http://localhost:3001/api/auth/login", json={"email": "claude-api@azcomputerguru.com", "password": "ClaudeAPI2026!@#"})
token = token_r.json()["token"]
headers = {"Authorization": "Bearer " + token, "Content-Type": "application/json"}
agent_id = "d28a1c90-47d7-448f-a287-197bc8892234"
chunk_size = 200000
chunks = [b64[i:i+chunk_size] for i in range(0, len(b64), chunk_size)]
print("Chunks:", len(chunks))
def send_cmd(cmd, timeout=60, wait=15):
r = requests.post(
"http://localhost:3001/api/agents/" + agent_id + "/command",
headers=headers,
json={"command_type": "powershell", "command": cmd, "timeout_seconds": timeout}
)
cmd_id = r.json()["command_id"]
time.sleep(wait)
r2 = requests.get("http://localhost:3001/api/commands/" + cmd_id, headers=headers)
return r2.json()
# Chunk 1: create file
cmd = "$b = [Convert]::FromBase64String('" + chunks[0] + "'); New-Item -ItemType Directory -Path C:/Temp -Force | Out-Null; [System.IO.File]::WriteAllBytes('C:/Temp/agent_chunk.bin', $b); Write-Output ('Chunk 1: ' + $b.Length.ToString() + ' bytes')"
d = send_cmd(cmd)
print("Chunk 1:", d["status"], d.get("stdout",""))
if d["status"] != "completed":
print("ERROR:", (d.get("stderr","") or "")[:300])
sys.exit(1)
# Append remaining chunks
for i, chunk in enumerate(chunks[1:], 2):
cmd = "$b = [Convert]::FromBase64String('" + chunk + "'); $f = [System.IO.File]::Open('C:/Temp/agent_chunk.bin', [System.IO.FileMode]::Append); $f.Write($b, 0, $b.Length); $f.Close(); Write-Output ('Chunk " + str(i) + ": ' + $b.Length.ToString() + ' bytes')"
d = send_cmd(cmd, wait=10)
print("Chunk", i, ":", d["status"], d.get("stdout",""))
if d["status"] != "completed":
print("ERROR:", (d.get("stderr","") or "")[:300])
sys.exit(1)
# Verify final size
d = send_cmd("(Get-Item C:/Temp/agent_chunk.bin).Length", wait=5)
print("Final size:", d.get("stdout","").strip(), "(expected 3577856)")
# Now create update script that stops service, replaces binary, starts service
update_cmd = """
$src = 'C:/Temp/agent_chunk.bin'
$dst = 'C:/Program Files/GuruRMM/gururmm-agent.exe'
$bak = 'C:/Program Files/GuruRMM/gururmm-agent.exe.bak'
Copy-Item $dst $bak -Force
Stop-Service GuruRMMAgent -Force
Start-Sleep -Seconds 2
Copy-Item $src $dst -Force
Start-Service GuruRMMAgent
Write-Output 'Agent updated and restarted'
"""
print("\nNow creating scheduled task to perform the update...")
sched_cmd = 'schtasks /create /tn AgentUpdate /tr "powershell.exe -ExecutionPolicy Bypass -Command \\"' + update_cmd.replace('\n', '; ').strip() + '\\"" /sc ONCE /st 00:00 /sd 01/01/2030 /ru SYSTEM /f'
d = send_cmd(sched_cmd, wait=5)
print("Sched task create:", d["status"], d.get("stdout",""), d.get("stderr","")[:200] if d.get("stderr") else "")
d = send_cmd("schtasks /run /tn AgentUpdate", wait=5)
print("Sched task run:", d["status"], d.get("stdout",""))
print("\nAgent will restart momentarily. Wait 30s then check connection.")

View File

@@ -0,0 +1,88 @@
"""Deploy agent binary to AD2 by writing base64 text chunks to a file, then decoding."""
import requests, json, time, sys, base64
# Read and encode the binary
with open("agent/target/release/gururmm-agent.exe", "rb") as f:
binary = f.read()
b64 = base64.b64encode(binary).decode('ascii')
print(f"Binary: {len(binary)} bytes, Base64: {len(b64)} chars")
# Auth
token_r = requests.post('http://172.16.3.30:3001/api/auth/login', json={
'email': 'claude-api@azcomputerguru.com',
'password': 'ClaudeAPI2026!@#'
})
token = token_r.json()['token']
headers = {'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json'}
agent_id = 'd28a1c90-47d7-448f-a287-197bc8892234'
def send_cmd(cmd, timeout=60, wait=10):
r = requests.post(
'http://172.16.3.30:3001/api/agents/' + agent_id + '/command',
headers=headers,
json={'command_type': 'powershell', 'command': cmd, 'timeout_seconds': timeout}
)
data = r.json()
cmd_id = data['command_id']
time.sleep(wait)
# Poll until complete
for attempt in range(10):
r2 = requests.get('http://172.16.3.30:3001/api/commands/' + cmd_id, headers=headers)
d = r2.json()
if d['status'] != 'running':
return d
time.sleep(5)
return d
# Step 1: Delete old file and create fresh one
print("Step 1: Preparing temp file...")
d = send_cmd("Remove-Item C:/Temp/agent.b64 -Force -ErrorAction SilentlyContinue; "
"New-Item -ItemType Directory -Path C:/Temp -Force | Out-Null; "
"'' | Set-Content C:/Temp/agent.b64 -NoNewline; "
"Write-Output 'Ready'", wait=8)
print(f" {d['status']}: {d.get('stdout','').strip()}")
if d['status'] != 'completed':
print(f" ERROR: {(d.get('stderr','') or '')[:300]}")
sys.exit(1)
# Step 2: Write base64 text in chunks
# Windows command line limit is ~32KB, keep chunks under 20KB to be safe
chunk_size = 20000
chunks = [b64[i:i+chunk_size] for i in range(0, len(b64), chunk_size)]
print(f"Step 2: Writing {len(chunks)} chunks of ~{chunk_size} chars each...")
for i, chunk in enumerate(chunks):
# Use Add-Content to append text (no base64 decode here, just text)
# Escape single quotes in base64 (shouldn't have any, but just in case)
safe_chunk = chunk.replace("'", "''")
cmd = f"Add-Content -Path C:/Temp/agent.b64 -Value '{safe_chunk}' -NoNewline; Write-Output 'chunk{i+1}ok'"
d = send_cmd(cmd, wait=5)
status = d['status']
stdout = d.get('stdout', '').strip()
if status != 'completed' or f'chunk{i+1}ok' not in stdout:
print(f" Chunk {i+1}/{len(chunks)} FAILED: {status} - {stdout}")
print(f" stderr: {(d.get('stderr','') or '')[:300]}")
sys.exit(1)
if (i+1) % 10 == 0 or i == 0 or i == len(chunks)-1:
print(f" Chunk {i+1}/{len(chunks)}: OK")
# Step 3: Verify base64 file size
print("Step 3: Verifying base64 file...")
d = send_cmd(f"$f = Get-Item C:/Temp/agent.b64; Write-Output $f.Length", wait=5)
remote_size = d.get('stdout', '').strip()
print(f" Remote b64 size: {remote_size} (expected: {len(b64)})")
# Step 4: Decode base64 file to binary
print("Step 4: Decoding base64 to binary...")
cmd = ("$b64 = Get-Content C:/Temp/agent.b64 -Raw; "
"$bytes = [Convert]::FromBase64String($b64); "
"[System.IO.File]::WriteAllBytes('C:/Temp/gururmm-agent-new.exe', $bytes); "
"$f = Get-Item C:/Temp/gururmm-agent-new.exe; "
"Write-Output ('Decoded: ' + $f.Length.ToString() + ' bytes')")
d = send_cmd(cmd, timeout=120, wait=15)
print(f" {d['status']}: {d.get('stdout','').strip()}")
if d.get('stderr'):
print(f" stderr: {(d.get('stderr','') or '')[:300]}")
print(f"\nExpected binary size: {len(binary)} bytes")
print("Done!")

View File

@@ -0,0 +1,35 @@
import requests, json, time, sys
# Auth
token_r = requests.post('http://172.16.3.30:3001/api/auth/login', json={
'email': 'claude-api@azcomputerguru.com',
'password': 'ClaudeAPI2026!@#'
})
token = token_r.json()['token']
headers = {'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json'}
agent_id = 'd28a1c90-47d7-448f-a287-197bc8892234'
# Send download command via PowerShell
cmd = (
"New-Item -ItemType Directory -Path C:/Temp -Force | Out-Null; "
"Invoke-WebRequest -Uri 'http://172.16.3.30/gururmm-agent-new.exe' "
"-OutFile 'C:/Temp/gururmm-agent-new.exe' -UseBasicParsing; "
"$f = Get-Item 'C:/Temp/gururmm-agent-new.exe'; "
"Write-Output ('Downloaded: ' + $f.Length.ToString() + ' bytes')"
)
r = requests.post(
'http://172.16.3.30:3001/api/agents/' + agent_id + '/command',
headers=headers,
json={'command_type': 'powershell', 'command': cmd, 'timeout_seconds': 120}
)
print('Send:', r.json())
cmd_id = r.json()['command_id']
# Wait and check
time.sleep(20)
r2 = requests.get('http://172.16.3.30:3001/api/commands/' + cmd_id, headers=headers)
d = r2.json()
print('Status:', d['status'])
print('stdout:', d.get('stdout', ''))
print('stderr:', (d.get('stderr', '') or '')[:500])

View File

@@ -0,0 +1,46 @@
import requests, json, time, sys
# Auth
token_r = requests.post('http://172.16.3.30:3001/api/auth/login', json={
'email': 'claude-api@azcomputerguru.com',
'password': 'ClaudeAPI2026!@#'
})
token = token_r.json()['token']
headers = {'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json'}
agent_id = 'd28a1c90-47d7-448f-a287-197bc8892234'
def send_cmd(cmd, timeout=120, wait=20):
r = requests.post(
'http://172.16.3.30:3001/api/agents/' + agent_id + '/command',
headers=headers,
json={'command_type': 'powershell', 'command': cmd, 'timeout_seconds': timeout}
)
data = r.json()
print('Sent:', data.get('status', 'error'), data.get('message', ''))
cmd_id = data['command_id']
time.sleep(wait)
r2 = requests.get('http://172.16.3.30:3001/api/commands/' + cmd_id, headers=headers)
d = r2.json()
print('Status:', d['status'])
print('stdout:', d.get('stdout', ''))
stderr = d.get('stderr', '') or ''
if stderr:
print('stderr:', stderr[:500])
print('exit_code:', d.get('exit_code'))
return d
# First, check what transfer tools are available on AD2
print("=== Checking available tools ===")
d = send_cmd("Get-Command scp,ssh,curl -ErrorAction SilentlyContinue | Select-Object Name,Source | Format-Table -AutoSize", wait=10)
print("\n=== Trying SCP from AD2 to RMM server ===")
# Use scp with StrictHostKeyChecking=no for automated transfer
cmd = (
"scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL "
"guru@172.16.3.30:/tmp/gururmm-agent-new.exe C:/Temp/gururmm-agent-new.exe 2>&1; "
"if (Test-Path C:/Temp/gururmm-agent-new.exe) { "
" $f = Get-Item C:/Temp/gururmm-agent-new.exe; "
" Write-Output ('File size: ' + $f.Length.ToString() + ' bytes') "
"} else { Write-Output 'File not found after SCP' }"
)
d = send_cmd(cmd, wait=30)

View File

@@ -10,16 +10,16 @@ use uuid::Uuid;
use crate::auth::AuthUser; use crate::auth::AuthUser;
use crate::db::{self, Command}; use crate::db::{self, Command};
use crate::ws::{CommandPayload, ServerMessage}; use crate::ws::{CommandPayload, CommandType, ServerMessage};
use crate::AppState; use crate::AppState;
/// Request to send a command to an agent /// Request to send a command to an agent
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct SendCommandRequest { pub struct SendCommandRequest {
/// Command type (shell, powershell, python, script) /// Command type (shell, powershell, python, script, claude_task)
pub command_type: String, pub command_type: String,
/// Command text to execute /// Command text to execute (also used as task description for claude_task)
pub command: String, pub command: String,
/// Timeout in seconds (optional, default 300) /// Timeout in seconds (optional, default 300)
@@ -27,6 +27,12 @@ pub struct SendCommandRequest {
/// Run as elevated/admin (optional, default false) /// Run as elevated/admin (optional, default false)
pub elevated: Option<bool>, pub elevated: Option<bool>,
/// Working directory for claude_task (optional, default C:\Shares\test)
pub working_directory: Option<String>,
/// Context files for claude_task (optional)
pub context_files: Option<Vec<String>>,
} }
/// Response after sending a command /// Response after sending a command
@@ -59,6 +65,18 @@ pub async fn send_command(
"Command sent by user" "Command sent by user"
); );
// Validate and build command type
let command_type = if CommandType::is_claude_task(&req.command_type) {
CommandType::new_claude_task(
req.command.clone(),
req.working_directory.clone(),
req.context_files.clone(),
)
} else {
CommandType::from_api_string(&req.command_type)
.map_err(|e| (StatusCode::BAD_REQUEST, e))?
};
// Verify agent exists // Verify agent exists
let _agent = db::get_agent_by_id(&state.db, agent_id) let _agent = db::get_agent_by_id(&state.db, agent_id)
.await .await
@@ -66,9 +84,10 @@ pub async fn send_command(
.ok_or((StatusCode::NOT_FOUND, "Agent not found".to_string()))?; .ok_or((StatusCode::NOT_FOUND, "Agent not found".to_string()))?;
// Create command record with user ID for audit trail // Create command record with user ID for audit trail
// Store the canonical db string format for consistency
let create = db::CreateCommand { let create = db::CreateCommand {
agent_id, agent_id,
command_type: req.command_type.clone(), command_type: command_type.as_db_string().to_string(),
command_text: req.command.clone(), command_text: req.command.clone(),
created_by: Some(user.user_id), created_by: Some(user.user_id),
}; };
@@ -80,10 +99,11 @@ pub async fn send_command(
// Check if agent is connected // Check if agent is connected
let agents = state.agents.read().await; let agents = state.agents.read().await;
if agents.is_connected(&agent_id) { if agents.is_connected(&agent_id) {
// Send command via WebSocket // Send command via WebSocket using the proper enum type
// This serializes as snake_case to match the agent's expected format
let cmd_msg = ServerMessage::Command(CommandPayload { let cmd_msg = ServerMessage::Command(CommandPayload {
id: command.id, id: command.id,
command_type: req.command_type, command_type,
command: req.command, command: req.command,
timeout_seconds: req.timeout_seconds, timeout_seconds: req.timeout_seconds,
elevated: req.elevated.unwrap_or(false), elevated: req.elevated.unwrap_or(false),

View File

@@ -193,10 +193,74 @@ pub struct NetworkStatePayload {
pub state_hash: String, pub state_hash: String,
} }
/// Types of commands that can be sent to agents.
/// Must match the agent's CommandType enum serialization format.
/// Uses snake_case to match the agent's #[serde(rename_all = "snake_case")].
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CommandType {
/// Shell command (cmd on Windows, sh on Unix)
Shell,
/// PowerShell command (Windows)
PowerShell,
/// Python script
Python,
/// Raw script execution
Script,
/// Claude Code task execution
ClaudeTask {
/// Task description for Claude Code
task: String,
/// Optional working directory
working_directory: Option<String>,
/// Optional context files
context_files: Option<Vec<String>>,
},
}
impl CommandType {
/// Parse a command type string from the API into the enum.
/// Accepts both snake_case ("power_shell") and common formats ("powershell").
/// Note: ClaudeTask requires additional fields - use `new_claude_task()` instead.
pub fn from_api_string(s: &str) -> Result<Self, String> {
match s.to_lowercase().as_str() {
"shell" => Ok(Self::Shell),
"powershell" | "power_shell" => Ok(Self::PowerShell),
"python" => Ok(Self::Python),
"script" => Ok(Self::Script),
"claude_task" | "claudetask" => Err(
"claude_task type requires task field - use the claude_task-specific API fields".to_string()
),
_ => Err(format!("Unknown command type: '{}'. Valid types: shell, powershell, python, script, claude_task", s)),
}
}
/// Check if a command type string represents a claude_task.
pub fn is_claude_task(s: &str) -> bool {
matches!(s.to_lowercase().as_str(), "claude_task" | "claudetask")
}
/// Create a ClaudeTask command type with the required fields.
pub fn new_claude_task(task: String, working_directory: Option<String>, context_files: Option<Vec<String>>) -> Self {
Self::ClaudeTask { task, working_directory, context_files }
}
/// Convert back to the string format stored in the database.
pub fn as_db_string(&self) -> &'static str {
match self {
Self::Shell => "shell",
Self::PowerShell => "powershell",
Self::Python => "python",
Self::Script => "script",
Self::ClaudeTask { .. } => "claude_task",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandPayload { pub struct CommandPayload {
pub id: Uuid, pub id: Uuid,
pub command_type: String, pub command_type: CommandType,
pub command: String, pub command: String,
pub timeout_seconds: Option<u64>, pub timeout_seconds: Option<u64>,
pub elevated: bool, pub elevated: bool,

View File

@@ -0,0 +1,80 @@
"""Create scheduled task on AD2 to swap agent binary and restart service."""
import requests, time
# Auth
token_r = requests.post('http://172.16.3.30:3001/api/auth/login', json={
'email': 'claude-api@azcomputerguru.com',
'password': 'ClaudeAPI2026!@#'
})
token = token_r.json()['token']
headers = {'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json'}
agent_id = 'd28a1c90-47d7-448f-a287-197bc8892234'
# Create a scheduled task to swap the binary and restart
# Uses schtasks for simplicity - runs a PowerShell script that:
# 1. Stops the agent service
# 2. Backs up old binary
# 3. Copies new binary in place
# 4. Starts the service
# 5. Writes result to file
cmd = (
"$script = @'\n"
"Stop-Service GuruRMMAgent -Force\n"
"Start-Sleep -Seconds 3\n"
"Copy-Item \"C:\\Program Files\\GuruRMM\\gururmm-agent.exe\" \"C:\\Program Files\\GuruRMM\\gururmm-agent.exe.bak\" -Force\n"
"Copy-Item \"C:\\Temp\\gururmm-agent-new.exe\" \"C:\\Program Files\\GuruRMM\\gururmm-agent.exe\" -Force\n"
"Start-Sleep -Seconds 1\n"
"Start-Service GuruRMMAgent\n"
"\"Agent updated at $(Get-Date)\" | Out-File C:\\Temp\\update_result.txt\n"
"'@\n"
"$script | Out-File C:/Temp/update_agent.ps1 -Encoding UTF8\n"
"Write-Output ('Script written: ' + (Get-Item C:/Temp/update_agent.ps1).Length.ToString() + ' bytes')"
)
print("Step 1: Writing update script to AD2...")
r = requests.post(
'http://172.16.3.30:3001/api/agents/' + agent_id + '/command',
headers=headers,
json={'command_type': 'powershell', 'command': cmd, 'timeout_seconds': 30}
)
print('Send:', r.json())
cmd_id = r.json()['command_id']
time.sleep(10)
r2 = requests.get('http://172.16.3.30:3001/api/commands/' + cmd_id, headers=headers)
d = r2.json()
print('Status:', d['status'])
print('stdout:', d.get('stdout', ''))
if d.get('stderr'):
print('stderr:', (d.get('stderr', '') or '')[:300])
if d['status'] != 'completed':
print("FAILED to write script")
exit(1)
# Step 2: Create and run scheduled task
print("\nStep 2: Creating scheduled task to run update...")
cmd2 = (
"schtasks /create /tn AgentBinaryUpdate /tr "
"\"powershell.exe -ExecutionPolicy Bypass -File C:\\Temp\\update_agent.ps1\" "
"/sc ONCE /st 00:00 /sd 01/01/2030 /ru SYSTEM /rl HIGHEST /f; "
"schtasks /run /tn AgentBinaryUpdate; "
"Write-Output 'Update task started - agent will restart momentarily'"
)
r = requests.post(
'http://172.16.3.30:3001/api/agents/' + agent_id + '/command',
headers=headers,
json={'command_type': 'powershell', 'command': cmd2, 'timeout_seconds': 30}
)
print('Send:', r.json())
cmd_id = r.json()['command_id']
time.sleep(10)
r2 = requests.get('http://172.16.3.30:3001/api/commands/' + cmd_id, headers=headers)
d = r2.json()
print('Status:', d['status'])
print('stdout:', d.get('stdout', ''))
if d.get('stderr'):
print('stderr:', (d.get('stderr', '') or '')[:300])
print("\nAgent will stop, binary will be swapped, then service restarts.")
print("Wait ~30 seconds then check if agent reconnects.")

Submodule projects/solverbot updated: 342fe0fdf4...60b2617651

20
tmp_backup_ad2.ps1 Normal file
View File

@@ -0,0 +1,20 @@
$ErrorActionPreference = "Stop"
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$uncRoot = "\\192.168.0.6\C$"
try {
New-PSDrive -Name AD2 -PSProvider FileSystem -Root $uncRoot -Credential $cred -ErrorAction Stop | Out-Null
Write-Output "=== DRIVE MAPPED ==="
# Step 3: Back up the current script
$src = "AD2:\Shares\test\scripts\Sync-FromNAS.ps1"
$bak = "AD2:\Shares\test\scripts\Sync-FromNAS.ps1.bak.2026-02-17"
Copy-Item -Path $src -Destination $bak -Force
Write-Output "=== BACKUP CREATED: $bak ==="
Remove-PSDrive -Name AD2 -ErrorAction SilentlyContinue
} catch {
Write-Output "ERROR: $_"
}

56
tmp_bulksync.ps1 Normal file
View File

@@ -0,0 +1,56 @@
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$uncRoot = "\\192.168.0.6\C$"
New-PSDrive -Name AD2 -PSProvider FileSystem -Root $uncRoot -Credential $cred -ErrorAction Stop | Out-Null
# Write a one-time bulk sync runner script
$bulkScript = @"
# One-time bulk sync with 86400 minute window (60 days)
# This catches all stranded files from the last ~35 days
# Auto-deletes itself after running
Set-Location "C:\Shares\test\scripts"
& powershell -NoProfile -ExecutionPolicy Bypass -File "C:\Shares\test\scripts\Sync-FromNAS.ps1" -MaxAgeMinutes 86400 -Verbose
# Log completion
Add-Content -Path "C:\Shares\test\scripts\sync-from-nas.log" -Value "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')) : BULK SYNC COMPLETE - one-time catchup finished"
# Clean up this trigger script
Remove-Item -Path "C:\Shares\test\scripts\BulkSync-OneTime.ps1" -Force -ErrorAction SilentlyContinue
"@
Set-Content -Path "AD2:\Shares\test\scripts\BulkSync-OneTime.ps1" -Value $bulkScript
Write-Output "[OK] Bulk sync script written to AD2:\Shares\test\scripts\BulkSync-OneTime.ps1"
# Now create a scheduled task on AD2 to run it immediately
# We can do this by writing a schtasks command file
$taskScript = @"
schtasks /create /tn "BulkSync-OneTime" /tr "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Shares\test\scripts\BulkSync-OneTime.ps1" /sc once /st 00:00 /ru INTRANET\sysadmin /rp "Paper123\!@#" /f
schtasks /run /tn "BulkSync-OneTime"
"@
Set-Content -Path "AD2:\Shares\test\scripts\run-bulk.cmd" -Value $taskScript
Write-Output "[OK] Task creation script written"
Write-Output ""
Write-Output "NOTE: The scheduled task needs to be triggered on AD2."
Write-Output "Attempting to use WMI to create process on AD2..."
# Try WMI process creation (may work even if WinRM doesnt)
try {
$proc = Invoke-WmiMethod -ComputerName 192.168.0.6 -Credential $cred -Class Win32_Process -Name Create -ArgumentList "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Shares\test\scripts\BulkSync-OneTime.ps1"
if ($proc.ReturnValue -eq 0) {
Write-Output "[OK] WMI process started on AD2\! PID: $($proc.ProcessId)"
} else {
Write-Output "[ERROR] WMI process creation failed with return value: $($proc.ReturnValue)"
}
} catch {
Write-Output "[ERROR] WMI failed: $_"
Write-Output "Trying schtasks approach..."
try {
$schResult = schtasks /create /s 192.168.0.6 /u INTRANET\sysadmin /p "Paper123\!@#" /tn "BulkSync-OneTime" /tr "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Shares\test\scripts\BulkSync-OneTime.ps1" /sc once /st 00:00 /ru INTRANET\sysadmin /rp "Paper123\!@#" /f 2>&1
Write-Output "schtasks create: $schResult"
$runResult = schtasks /run /s 192.168.0.6 /u INTRANET\sysadmin /p "Paper123\!@#" /tn "BulkSync-OneTime" 2>&1
Write-Output "schtasks run: $runResult"
} catch {
Write-Output "[ERROR] schtasks also failed: $_"
}
}
Remove-PSDrive -Name AD2 -ErrorAction SilentlyContinue

29
tmp_bulksync2.ps1 Normal file
View File

@@ -0,0 +1,29 @@
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$uncPath = "\\192.168.0.6\C$\Shares\test\scripts"
# Map net use
net use "\\192.168.0.6\C$" /user:INTRANET\sysadmin ("Paper123" + [char]33 + "@#") 2>&1 | Out-Null
# Write the bulk sync script using direct UNC path
$bulkContent = @"
Set-Location "C:\Shares\test\scripts"
& powershell -NoProfile -ExecutionPolicy Bypass -File "C:\Shares\test\scripts\Sync-FromNAS.ps1" -MaxAgeMinutes 86400 -Verbose
"@
[System.IO.File]::WriteAllText("\\192.168.0.6\C$\Shares\test\scripts\BulkSync-OneTime.ps1", $bulkContent)
Write-Output "[OK] BulkSync script written via System.IO"
# Verify it exists
$exists = Test-Path "\\192.168.0.6\C$\Shares\test\scripts\BulkSync-OneTime.ps1"
Write-Output "File exists: $exists"
# Now trigger it via WMI
$proc = Invoke-WmiMethod -ComputerName 192.168.0.6 -Credential $cred -Class Win32_Process -Name Create -ArgumentList "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Shares\test\scripts\BulkSync-OneTime.ps1"
if ($proc.ReturnValue -eq 0) {
Write-Output "[OK] Bulk sync started on AD2\! PID: $($proc.ProcessId)"
} else {
Write-Output "[ERROR] Failed to start: return value $($proc.ReturnValue)"
}
net use "\\192.168.0.6\C$" /delete 2>&1 | Out-Null

13
tmp_check2.ps1 Normal file
View File

@@ -0,0 +1,13 @@
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
# Check if still running
$procs = Get-WmiObject -ComputerName 192.168.0.6 -Credential $cred -Class Win32_Process -Filter "Name='powershell.exe'" 2>$null
$syncRunning = $false
foreach ($p in $procs) {
if ($p.CommandLine -match "BulkSync|86400") {
Write-Output "Still running: PID=$($p.ProcessId)"
$syncRunning = $true
}
}
if (-not $syncRunning) { Write-Output "Bulk sync has completed\!" }

18
tmp_check3.ps1 Normal file
View File

@@ -0,0 +1,18 @@
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
# Read the last 40 lines of the sync log
$logContent = [System.IO.File]::ReadAllText("\\192.168.0.6\C$\Shares\test\scripts\sync-from-nas.log")
$lines = $logContent -split "`n"
$lastLines = $lines | Select-Object -Last 40
Write-Output "=== LAST 40 LOG LINES ==="
$lastLines | ForEach-Object { Write-Output $_ }
# Check process status
$procs = Get-WmiObject -ComputerName 192.168.0.6 -Credential $cred -Class Win32_Process -Filter "Name='powershell.exe'" 2>$null
$syncRunning = $false
foreach ($p in $procs) {
if ($p.CommandLine -match "BulkSync|86400") { $syncRunning = $true }
}
Write-Output ""
if ($syncRunning) { Write-Output "STATUS: Still running..." } else { Write-Output "STATUS: COMPLETED" }

18
tmp_check4.ps1 Normal file
View File

@@ -0,0 +1,18 @@
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
# Read the last 50 lines of the sync log
$logContent = [System.IO.File]::ReadAllText("\\192.168.0.6\C$\Shares\test\scripts\sync-from-nas.log")
$lines = $logContent -split "`n"
$lastLines = $lines | Select-Object -Last 50
Write-Output "=== LAST 50 LOG LINES ==="
$lastLines | ForEach-Object { Write-Output $_ }
# Check process status
$procs = Get-WmiObject -ComputerName 192.168.0.6 -Credential $cred -Class Win32_Process -Filter "Name='powershell.exe'" 2>$null
$syncRunning = $false
foreach ($p in $procs) {
if ($p.CommandLine -match "BulkSync|86400") { $syncRunning = $true }
}
Write-Output ""
if ($syncRunning) { Write-Output "[...] STATUS: Still running..." } else { Write-Output "[OK] STATUS: COMPLETED" }

18
tmp_check_proc.ps1 Normal file
View File

@@ -0,0 +1,18 @@
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
# Check if the process is still running
$proc = Get-WmiObject -ComputerName 192.168.0.6 -Credential $cred -Class Win32_Process -Filter "ProcessId=15856" 2>$null
if ($proc) {
Write-Output "Process 15856 still running: $($proc.CommandLine)"
} else {
Write-Output "Process 15856 has completed"
}
# Check for any powershell processes running our script
$procs = Get-WmiObject -ComputerName 192.168.0.6 -Credential $cred -Class Win32_Process -Filter "Name='powershell.exe'" 2>$null
foreach ($p in $procs) {
if ($p.CommandLine -match "Sync-FromNAS|BulkSync") {
Write-Output "Found sync process: PID=$($p.ProcessId) CMD=$($p.CommandLine)"
}
}

21
tmp_connect_ad2.ps1 Normal file
View File

@@ -0,0 +1,21 @@
$ErrorActionPreference = "Stop"
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$uncRoot = "\\192.168.0.6\C$"
try {
New-PSDrive -Name AD2 -PSProvider FileSystem -Root $uncRoot -Credential $cred -ErrorAction Stop
Write-Output "=== DRIVE MAPPED ==="
$scriptPath = "AD2:\Shares\test\scripts\Sync-FromNAS.ps1"
if (Test-Path $scriptPath) {
Write-Output "=== SCRIPT CONTENT START ==="
Get-Content $scriptPath -Raw
Write-Output "=== SCRIPT CONTENT END ==="
} else {
Write-Output "Script not found"
}
Remove-PSDrive -Name AD2 -ErrorAction SilentlyContinue
} catch {
Write-Output "ERROR: $_"
}

37
tmp_fix2.ps1 Normal file
View File

@@ -0,0 +1,37 @@
$ErrorActionPreference = "Stop"
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$uncRoot = "\\192.168.0.6\C$"
try {
New-PSDrive -Name AD2 -PSProvider FileSystem -Root $uncRoot -Credential $cred -ErrorAction Stop | Out-Null
$scriptPath = "AD2:\Shares\test\scripts\Sync-FromNAS.ps1"
$lines = Get-Content $scriptPath
# Fix line 70 (0-indexed: 69) - the Copy-FromNAS SCP line
$oldLine = $lines[69]
Write-Output "OLD LINE 70: $oldLine"
# The correct line should have backtick-quoted remote path and quoted local path
$lines[69] = ' $result = & $SCP -O -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile="C:\Shares\test\scripts\.ssh\known_hosts" "${NAS_USER}@${NAS_IP}:`"${RemotePath}`"" "$LocalPath" 2>&1'
Write-Output "NEW LINE 70: $($lines[69])"
# Write back
Set-Content -Path $scriptPath -Value $lines
Write-Output "=== LINE 70 FIXED ==="
# Verify both SCP lines are now correct
$verify = Get-Content $scriptPath
Write-Output "=== FINAL SCP LINES ==="
for ($i = 0; $i -lt $verify.Count; $i++) {
if ($verify[$i] -match "result.*SCP") {
Write-Output ("{0,3}: {1}" -f ($i+1), $verify[$i].TrimEnd())
}
}
Remove-PSDrive -Name AD2 -ErrorAction SilentlyContinue
} catch {
Write-Output "ERROR: $_"
Write-Output $_.ScriptStackTrace
}

58
tmp_fix_script.ps1 Normal file
View File

@@ -0,0 +1,58 @@
$ErrorActionPreference = "Stop"
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$uncRoot = "\\192.168.0.6\C$"
try {
New-PSDrive -Name AD2 -PSProvider FileSystem -Root $uncRoot -Credential $cred -ErrorAction Stop | Out-Null
Write-Output "=== DRIVE MAPPED ==="
$scriptPath = "AD2:\Shares\test\scripts\Sync-FromNAS.ps1"
$content = Get-Content $scriptPath -Raw
# Fix 1: Invoke-NASCommand - add user@host target
$old1 = '$result = & $SSH -i "C:\Users\sysadmin\.ssh\id_ed25519" -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new $Command 2>&1'
$new1 = '$result = & $SSH -i "C:\Users\sysadmin\.ssh\id_ed25519" -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${NAS_USER}@${NAS_IP}" $Command 2>&1'
$content = $content.Replace($old1, $new1)
# Fix 2: Copy-FromNAS - fix the SCP line (it has the if statement on same line + unquoted paths)
$old2 = '$result = & $SCP -O -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile="C:\Shares\test\scripts\.ssh\known_hosts" "${NAS_USER}@${NAS_IP}:$RemotePath" $LocalPath 2>&1 if ($LASTEXITCODE -ne 0) {'
$new2 = @"
`$result = & `$SCP -O -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile="C:\Shares\test\scripts\.ssh\known_hosts" "`${NAS_USER}@`${NAS_IP}:`"`${RemotePath}`"" "`$LocalPath" 2>&1
if (`$LASTEXITCODE -ne 0) {
"@
$content = $content.Replace($old2, $new2)
# Fix 3: Fix the error message in Copy-FromNAS (PUSH -> PULL)
$old3 = 'Write-Log " SCP PUSH ERROR (exit $LASTEXITCODE): $errorMsg"'
# Only fix the FIRST occurrence (in Copy-FromNAS). Use a targeted approach.
# Since we already fixed the structure, just do a simple replace of the first occurrence
$idx = $content.IndexOf($old3)
if ($idx -ge 0) {
$new3 = 'Write-Log " SCP PULL ERROR (exit $LASTEXITCODE): $errorMsg"'
$content = $content.Substring(0, $idx) + $new3 + $content.Substring($idx + $old3.Length)
}
# Fix 4: Copy-ToNAS - quote both paths
$old4 = '$result = & $SCP -O -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile="C:\Shares\test\scripts\.ssh\known_hosts" $LocalPath "${NAS_USER}@${NAS_IP}:$RemotePath" 2>&1'
$new4 = '$result = & $SCP -O -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile="C:\Shares\test\scripts\.ssh\known_hosts" "$LocalPath" "${NAS_USER}@${NAS_IP}:`"${RemotePath}`"" 2>&1'
$content = $content.Replace($old4, $new4)
# Write the fixed script
Set-Content -Path $scriptPath -Value $content -NoNewline
Write-Output "=== SCRIPT UPDATED ==="
# Verify the fixes
$verify = Get-Content $scriptPath -Raw
Write-Output "=== VERIFY: SCP LINES ==="
$verify -split "`n" | Where-Object { $_ -match "result = .* SCP" } | ForEach-Object { Write-Output (" " + $_.Trim()) }
Write-Output "=== VERIFY: SSH LINE ==="
$verify -split "`n" | Where-Object { $_ -match "result = .* SSH" } | ForEach-Object { Write-Output (" " + $_.Trim()) }
Write-Output "=== VERIFY: ERROR MESSAGES ==="
$verify -split "`n" | Where-Object { $_ -match "SCP (PULL|PUSH) ERROR" } | ForEach-Object { Write-Output (" " + $_.Trim()) }
Remove-PSDrive -Name AD2 -ErrorAction SilentlyContinue
} catch {
Write-Output "ERROR: $_"
Write-Output $_.ScriptStackTrace
}

7
tmp_grep.ps1 Normal file
View File

@@ -0,0 +1,7 @@
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$cmd = "cmd /c powershell -NoProfile -Command \"Get-Content C:\Shares\test\scripts\sync-from-nas.log | Select-String -Pattern 'Starting sync|Max age: 86400|Sync complete|No new DAT|Found.*DAT|PULL=|ambiguous' | Select-Object -Last 30 | ForEach-Object { $_.Line } > C:\Shares\test\scripts\tail-grep.txt 2>&1\""
$r = Invoke-WmiMethod -ComputerName 192.168.0.6 -Credential $cred -Class Win32_Process -Name Create -ArgumentList $cmd
Write-Output "PID: $($r.ProcessId)"
Start-Sleep -Seconds 15
Write-Output ([System.IO.File]::ReadAllText("\\192.168.0.6\C$\Shares\test\scripts\tail-grep.txt"))

14
tmp_grep2.ps1 Normal file
View File

@@ -0,0 +1,14 @@
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$filterScript = 'Get-Content C:\Shares\test\scripts\sync-from-nas.log | Select-String -Pattern "Starting sync|Max age: 86400|Sync complete|No new DAT|Found.*DAT|PULL=|ambiguous|Copied to station|Copied report" | Select-Object -Last 40 | ForEach-Object { $_.Line } | Out-File C:\Shares\test\scripts\tail-grep.txt -Encoding utf8'
[System.IO.File]::WriteAllText("\\192.168.0.6\C$\Shares\test\scripts\filter-log.ps1", $filterScript)
$r = Invoke-WmiMethod -ComputerName 192.168.0.6 -Credential $cred -Class Win32_Process -Name Create -ArgumentList "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Shares\test\scripts\filter-log.ps1"
Write-Output "PID: $($r.ProcessId)"
Start-Sleep -Seconds 20
try {
Write-Output ([System.IO.File]::ReadAllText("\\192.168.0.6\C$\Shares\test\scripts\tail-grep.txt"))
} catch {
Write-Output "File not ready: $_"
}

24
tmp_inspect.ps1 Normal file
View File

@@ -0,0 +1,24 @@
$ErrorActionPreference = "Stop"
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$uncRoot = "\\192.168.0.6\C$"
try {
New-PSDrive -Name AD2 -PSProvider FileSystem -Root $uncRoot -Credential $cred -ErrorAction Stop | Out-Null
Write-Output "=== DRIVE MAPPED ==="
$scriptPath = "AD2:\Shares\test\scripts\Sync-FromNAS.ps1"
$content = Get-Content $scriptPath -Raw
# Show the original problematic lines
Write-Output "=== ORIGINAL Copy-FromNAS SCP LINE ==="
$content -split "`n" | Where-Object { $_ -match "SCP.*RemotePath.*LocalPath|SCP.*LocalPath.*RemotePath" } | ForEach-Object { Write-Output $_.Trim() }
Write-Output "=== ORIGINAL Invoke-NASCommand LINE ==="
$content -split "`n" | Where-Object { $_ -match "result = .* \" } | ForEach-Object { Write-Output $_.Trim() }
Remove-PSDrive -Name AD2 -ErrorAction SilentlyContinue
} catch {
Write-Output "ERROR: $_"
}

20
tmp_lines.ps1 Normal file
View File

@@ -0,0 +1,20 @@
$ErrorActionPreference = "Stop"
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$uncRoot = "\\192.168.0.6\C$"
try {
New-PSDrive -Name AD2 -PSProvider FileSystem -Root $uncRoot -Credential $cred -ErrorAction Stop | Out-Null
$scriptPath = "AD2:\Shares\test\scripts\Sync-FromNAS.ps1"
$lines = Get-Content $scriptPath
# Show lines 70-95 (around Copy-FromNAS SCP area)
Write-Output "=== Lines 60-95 ==="
for ($i = 59; $i -lt 95 -and $i -lt $lines.Count; $i++) {
Write-Output ("{0,3}: {1}" -f ($i+1), $lines[$i])
}
Remove-PSDrive -Name AD2 -ErrorAction SilentlyContinue
} catch {
Write-Output "ERROR: $_"
}

9
tmp_readlog.ps1 Normal file
View File

@@ -0,0 +1,9 @@
# Read sync log - last 60 lines
try {
$log = [System.IO.File]::ReadAllText("\\192.168.0.6\C$\Shares\test\scripts\sync-from-nas.log")
$logLines = $log -split "`n"
Write-Output "=== SYNC LOG (last 60 lines) ==="
$logLines | Select-Object -Last 60 | ForEach-Object { Write-Output $_ }
} catch {
Write-Output "ERROR reading log: $_"
}

0
tmp_readlog2.ps1 Normal file
View File

18
tmp_readtail.ps1 Normal file
View File

@@ -0,0 +1,18 @@
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
# Write a small script on AD2 to create the tail file
$tailScript = "Get-Content C:\Shares\test\scripts\sync-from-nas.log -Tail 80 | Out-File C:\Shares\test\scripts\sync-tail.txt -Encoding utf8; Get-Content C:\Shares\test\_SYNC_STATUS.txt | Out-File C:\Shares\test\scripts\sync-tail.txt -Append -Encoding utf8"
[System.IO.File]::WriteAllText("\\192.168.0.6\C$\Shares\test\scripts\read-tail.ps1", $tailScript)
# Run it via WMI
$cmd = "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Shares\test\scripts\read-tail.ps1"
$result = Invoke-WmiMethod -ComputerName 192.168.0.6 -Credential $cred -Class Win32_Process -Name Create -ArgumentList $cmd
Write-Output "WMI result: $($result.ReturnValue) PID: $($result.ProcessId)"
# Wait for it to finish
Start-Sleep -Seconds 5
# Read the tail file
$tailContent = [System.IO.File]::ReadAllText("\\192.168.0.6\C$\Shares\test\scripts\sync-tail.txt")
Write-Output $tailContent

10
tmp_summary.ps1 Normal file
View File

@@ -0,0 +1,10 @@
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$script = '$log = Get-Content C:\Shares\test\scripts\sync-from-nas.log; $bulk = $log | Where-Object { $_ -match "2026-02-17 16:(4|5)" }; $out = @(); $out += "=== FIRST 30 LINES ==="; $out += ($bulk | Select-Object -First 30); $out += ""; $out += "=== LAST 20 LINES ==="; $out += ($bulk | Select-Object -Last 20); $out += ""; $out += "=== SUMMARY ==="; $out += "Total lines: $($bulk.Count)"; $out += "Pushed: $(($bulk | Where-Object { $_ -match "Pushed:" }).Count)"; $out += "Errors: $(($bulk | Where-Object { $_ -match "ERROR" }).Count)"; $out += "Ambiguous: $(($bulk | Where-Object { $_ -match "ambiguous" }).Count)"; $out += "No such file: $(($bulk | Where-Object { $_ -match "No such file" }).Count)"; $out += "Copied station: $(($bulk | Where-Object { $_ -match "Copied to station" }).Count)"; $out += "Copied report: $(($bulk | Where-Object { $_ -match "Copied report" }).Count)"; $out | Out-File C:\Shares\test\scripts\sync-summary.txt -Encoding utf8'
[System.IO.File]::WriteAllText("\\192.168.0.6\C$\Shares\test\scripts\make-summary.ps1", $script)
$r = Invoke-WmiMethod -ComputerName 192.168.0.6 -Credential $cred -Class Win32_Process -Name Create -ArgumentList "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Shares\test\scripts\make-summary.ps1"
Write-Output "PID: $($r.ProcessId)"
Start-Sleep -Seconds 10
Write-Output ([System.IO.File]::ReadAllText("\\192.168.0.6\C$\Shares\test\scripts\sync-summary.txt"))

29
tmp_syntax.ps1 Normal file
View File

@@ -0,0 +1,29 @@
$ErrorActionPreference = "Stop"
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$uncRoot = "\\192.168.0.6\C$"
try {
New-PSDrive -Name AD2 -PSProvider FileSystem -Root $uncRoot -Credential $cred -ErrorAction Stop | Out-Null
$scriptPath = "AD2:\Shares\test\scripts\Sync-FromNAS.ps1"
$content = Get-Content $scriptPath -Raw
# Syntax check via parsing the content string
$parseErrors = $null
[System.Management.Automation.Language.Parser]::ParseInput($content, [ref]$null, [ref]$parseErrors)
if ($parseErrors.Count -eq 0) {
Write-Output "[OK] Script parses OK - no syntax errors"
} else {
foreach ($err in $parseErrors) {
Write-Output "[ERROR] PARSE ERROR: $($err.Message) at line $($err.Extent.StartLineNumber)"
}
}
# Count total lines
$lineCount = ($content -split "`n").Count
Write-Output "Total lines: $lineCount"
Remove-PSDrive -Name AD2 -ErrorAction SilentlyContinue
} catch {
Write-Output "ERROR: $_"
}

11
tmp_syntax2.ps1 Normal file
View File

@@ -0,0 +1,11 @@
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$uncRoot = "\\192.168.0.6\C$"
New-PSDrive -Name AD2 -PSProvider FileSystem -Root $uncRoot -Credential $cred -ErrorAction Stop | Out-Null
$content = Get-Content "AD2:\Shares\test\scripts\Sync-FromNAS.ps1" -Raw
$tokens = $null; $parseErrors = $null
[void][System.Management.Automation.Language.Parser]::ParseInput($content, [ref]$tokens, [ref]$parseErrors)
Write-Output "Parse errors: $($parseErrors.Count)"
if ($parseErrors.Count -gt 0) { $parseErrors | ForEach-Object { Write-Output " $_" } }
else { Write-Output "[OK] No syntax errors detected" }
Remove-PSDrive -Name AD2 -ErrorAction SilentlyContinue

6
tmp_tail.ps1 Normal file
View File

@@ -0,0 +1,6 @@
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$r = Invoke-WmiMethod -ComputerName 192.168.0.6 -Credential $cred -Class Win32_Process -Name Create -ArgumentList "cmd /c powershell -NoProfile -Command Get-Content C:\Shares\test\scripts\sync-from-nas.log -Tail 40 > C:\Shares\test\scripts\tail40.txt 2>&1"
Write-Output "PID: $($r.ProcessId) Return: $($r.ReturnValue)"
Start-Sleep -Seconds 8
Write-Output ([System.IO.File]::ReadAllText("\\192.168.0.6\C$\Shares\test\scripts\tail40.txt"))

18
tmp_test.ps1 Normal file
View File

@@ -0,0 +1,18 @@
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$uncRoot = "\\192.168.0.6\C$"
New-PSDrive -Name AD2 -PSProvider FileSystem -Root $uncRoot -Credential $cred -ErrorAction Stop | Out-Null
# First, do a dry run to see what files are stranded on the NAS
Write-Output "=== DRY RUN: Bulk sync with 86400 minute window (60 days) ==="
$dryRunOutput = & powershell -NoProfile -ExecutionPolicy Bypass -Command {
# We cannot run the script directly on AD2 from here, so we will invoke it remotely
}
# Actually we need to run this ON AD2. Lets check if we can invoke it via the mapped drive.
# The script needs to run locally on AD2 (it calls SCP/SSH). We need WinRM or SSH for that.
# Since WinRM is blocked from this machine, lets try another approach.
# Check: can we SSH to AD2 with the key from this machine?
Write-Output "=== Testing SSH to NAS directly from this machine ==="
Remove-PSDrive -Name AD2 -ErrorAction SilentlyContinue

33
tmp_verify.ps1 Normal file
View File

@@ -0,0 +1,33 @@
$ErrorActionPreference = "Stop"
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$uncRoot = "\\192.168.0.6\C$"
try {
New-PSDrive -Name AD2 -PSProvider FileSystem -Root $uncRoot -Credential $cred -ErrorAction Stop | Out-Null
$scriptPath = "AD2:\Shares\test\scripts\Sync-FromNAS.ps1"
$content = Get-Content $scriptPath -Raw
# Show the Copy-FromNAS function
$lines = $content -split "`n"
$inFunc = $false
$funcName = ""
foreach ($line in $lines) {
if ($line -match "^function (Copy-FromNAS|Copy-ToNAS|Invoke-NASCommand)") {
$inFunc = $true
$funcName = $Matches[1]
Write-Output "=== $funcName ==="
}
if ($inFunc) {
Write-Output $line.TrimEnd()
if ($line.Trim() -eq "}" -and $funcName) {
$inFunc = $false
Write-Output ""
}
}
}
Remove-PSDrive -Name AD2 -ErrorAction SilentlyContinue
} catch {
Write-Output "ERROR: $_"
}

32
tmp_verify2.ps1 Normal file
View File

@@ -0,0 +1,32 @@
$ErrorActionPreference = "Stop"
$pass = ConvertTo-SecureString ("Paper123" + [char]33 + "@#") -AsPlainText -Force
$cred = New-Object PSCredential("INTRANET\sysadmin", $pass)
$uncRoot = "\\192.168.0.6\C$"
try {
New-PSDrive -Name AD2 -PSProvider FileSystem -Root $uncRoot -Credential $cred -ErrorAction Stop | Out-Null
$scriptPath = "AD2:\Shares\test\scripts\Sync-FromNAS.ps1"
$lines = Get-Content $scriptPath
Write-Output "=== FULL VERIFICATION: Lines 48-100 ==="
for ($i = 47; $i -lt 100 -and $i -lt $lines.Count; $i++) {
Write-Output ("{0,3}: {1}" -f ($i+1), $lines[$i])
}
# Also check the script parses without errors
Write-Output ""
Write-Output "=== SYNTAX CHECK ==="
$parseErrors = $null
[System.Management.Automation.Language.Parser]::ParseFile("AD2:\Shares\test\scripts\Sync-FromNAS.ps1", [ref]$null, [ref]$parseErrors)
if ($parseErrors.Count -eq 0) {
Write-Output "Script parses OK - no syntax errors"
} else {
foreach ($err in $parseErrors) {
Write-Output "PARSE ERROR: $($err.Message) at line $($err.Extent.StartLineNumber)"
}
}
Remove-PSDrive -Name AD2 -ErrorAction SilentlyContinue
} catch {
Write-Output "ERROR: $_"
}