Initial commit
This commit is contained in:
253
scripts/winrm/winrm_executor_v2.py
Executable file
253
scripts/winrm/winrm_executor_v2.py
Executable file
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Improved WinRM Script Executor
|
||||
Handles large scripts by uploading them in chunks
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
import time
|
||||
import base64
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from winrm import Session
|
||||
import logging
|
||||
|
||||
|
||||
class WinRMExecutorV2:
|
||||
def __init__(self, host, username, password, port=5985, use_https=False):
|
||||
self.host = host
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.port = port
|
||||
self.use_https = use_https
|
||||
self.session = None
|
||||
|
||||
# Setup logging
|
||||
self.setup_logging()
|
||||
|
||||
def setup_logging(self):
|
||||
"""Setup logging configuration"""
|
||||
log_dir = Path(__file__).parent / "../../logs"
|
||||
log_dir.mkdir(exist_ok=True)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
log_file = log_dir / f"winrm-v2-{self.host}-{timestamp}.log"
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(log_file),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.logger.info(f"Starting WinRM session to {self.host}")
|
||||
|
||||
def connect(self):
|
||||
"""Establish WinRM connection"""
|
||||
try:
|
||||
protocol = 'https' if self.use_https else 'http'
|
||||
endpoint = f'{protocol}://{self.host}:{self.port}/wsman'
|
||||
|
||||
self.logger.info(f"Connecting to {endpoint}")
|
||||
|
||||
self.session = Session(
|
||||
endpoint,
|
||||
auth=(self.username, self.password),
|
||||
transport='ntlm' if not self.use_https else 'ssl',
|
||||
server_cert_validation='ignore' if self.use_https else 'validate'
|
||||
)
|
||||
|
||||
# Test connection with a simple command
|
||||
result = self.session.run_cmd('echo Connection test')
|
||||
if result.status_code == 0:
|
||||
self.logger.info("✅ WinRM connection established successfully")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"❌ Connection test failed: {result.std_err.decode()}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Failed to connect: {e}")
|
||||
return False
|
||||
|
||||
def upload_script_chunked(self, local_script_path, remote_script_name):
|
||||
"""Upload script in chunks to avoid command line length limits"""
|
||||
try:
|
||||
# Read the local script
|
||||
with open(local_script_path, 'r', encoding='utf-8') as f:
|
||||
script_content = f.read()
|
||||
|
||||
self.logger.info(f"Uploading script in chunks: {local_script_path} -> {remote_script_name}")
|
||||
|
||||
# Encode content as base64 to avoid special character issues
|
||||
script_bytes = script_content.encode('utf-8')
|
||||
script_b64 = base64.b64encode(script_bytes).decode('ascii')
|
||||
|
||||
# Split into chunks of 8000 chars (safe for command line)
|
||||
chunk_size = 8000
|
||||
chunks = [script_b64[i:i+chunk_size] for i in range(0, len(script_b64), chunk_size)]
|
||||
|
||||
self.logger.info(f"Uploading {len(chunks)} chunks...")
|
||||
|
||||
# Create the file and append chunks
|
||||
for i, chunk in enumerate(chunks):
|
||||
if i == 0:
|
||||
# First chunk - create file
|
||||
ps_command = f"""
|
||||
$chunk = '{chunk}'
|
||||
$chunk | Out-File -FilePath '{remote_script_name}.b64' -Encoding ASCII
|
||||
"""
|
||||
else:
|
||||
# Subsequent chunks - append
|
||||
ps_command = f"""
|
||||
$chunk = '{chunk}'
|
||||
$chunk | Out-File -FilePath '{remote_script_name}.b64' -Encoding ASCII -Append
|
||||
"""
|
||||
|
||||
result = self.session.run_ps(ps_command)
|
||||
|
||||
if result.status_code != 0:
|
||||
self.logger.error(f"Failed to upload chunk {i+1}: {result.std_err.decode()}")
|
||||
return False
|
||||
|
||||
if (i + 1) % 10 == 0:
|
||||
self.logger.info(f"Uploaded {i+1}/{len(chunks)} chunks...")
|
||||
|
||||
# Decode the base64 file to create the final script
|
||||
decode_command = f"""
|
||||
$b64Content = Get-Content '{remote_script_name}.b64' -Raw
|
||||
$bytes = [System.Convert]::FromBase64String($b64Content)
|
||||
|
||||
# Write with UTF-8 BOM for Chinese Windows compatibility
|
||||
$utf8BOM = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllBytes('{remote_script_name}', $bytes)
|
||||
|
||||
# Clean up temporary file
|
||||
Remove-Item '{remote_script_name}.b64' -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Host "Script uploaded successfully to {remote_script_name}"
|
||||
"""
|
||||
|
||||
result = self.session.run_ps(decode_command)
|
||||
|
||||
if result.status_code == 0:
|
||||
self.logger.info("✅ Script uploaded and decoded successfully")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"Failed to decode script: {result.std_err.decode()}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Script upload failed: {e}")
|
||||
return False
|
||||
|
||||
def execute_script(self, remote_script_name):
|
||||
"""Execute the uploaded PowerShell script"""
|
||||
try:
|
||||
self.logger.info(f"Executing PowerShell script: {remote_script_name}")
|
||||
|
||||
# Execute the script
|
||||
execute_command = f"PowerShell -NoProfile -ExecutionPolicy Bypass -File '{remote_script_name}'"
|
||||
result = self.session.run_cmd(execute_command)
|
||||
|
||||
# Output results in real-time
|
||||
if result.std_out:
|
||||
output = result.std_out.decode('utf-8', errors='replace')
|
||||
self.logger.info("Script Output:")
|
||||
for line in output.split('\n'):
|
||||
if line.strip():
|
||||
print(line)
|
||||
|
||||
if result.std_err:
|
||||
error = result.std_err.decode('utf-8', errors='replace')
|
||||
if error.strip():
|
||||
self.logger.warning("Script Errors:")
|
||||
for line in error.split('\n'):
|
||||
if line.strip():
|
||||
print(f"ERROR: {line}")
|
||||
|
||||
success = result.status_code == 0
|
||||
status = "✅ SUCCESS" if success else f"❌ FAILED (exit code: {result.status_code})"
|
||||
self.logger.info(f"Script execution completed: {status}")
|
||||
|
||||
return success
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Script execution failed: {e}")
|
||||
return False
|
||||
|
||||
def upload_and_execute_script(self, local_script_path, remote_script_name=None):
|
||||
"""Upload and execute a PowerShell script using chunked upload"""
|
||||
if not self.session:
|
||||
self.logger.error("No active WinRM session")
|
||||
return False
|
||||
|
||||
# Generate remote script name if not provided
|
||||
if not remote_script_name:
|
||||
script_name = Path(local_script_path).name
|
||||
remote_script_name = f"C:\\Users\\{self.username}\\{script_name}"
|
||||
|
||||
# Upload script
|
||||
if not self.upload_script_chunked(local_script_path, remote_script_name):
|
||||
return False
|
||||
|
||||
# Execute script
|
||||
return self.execute_script(remote_script_name)
|
||||
|
||||
def close(self):
|
||||
"""Close the WinRM session"""
|
||||
if self.session:
|
||||
self.logger.info("Closing WinRM session")
|
||||
self.session = None
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Execute PowerShell scripts via WinRM (v2)')
|
||||
parser.add_argument('--host', default=os.getenv('HOST', '192.168.88.108'),
|
||||
help='Target host')
|
||||
parser.add_argument('--username', default=os.getenv('USER_NAME', 'Admin'),
|
||||
help='Username')
|
||||
parser.add_argument('--password', default=os.getenv('PASS', 'P@ssw0rd!'),
|
||||
help='Password')
|
||||
parser.add_argument('--script', required=True,
|
||||
help='PowerShell script file to execute')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Create executor
|
||||
executor = WinRMExecutorV2(
|
||||
host=args.host,
|
||||
username=args.username,
|
||||
password=args.password
|
||||
)
|
||||
|
||||
try:
|
||||
# Connect
|
||||
if not executor.connect():
|
||||
sys.exit(1)
|
||||
|
||||
# Execute script
|
||||
script_path = Path(args.script)
|
||||
if not script_path.exists():
|
||||
print(f"❌ Script file not found: {script_path}")
|
||||
sys.exit(1)
|
||||
|
||||
success = executor.upload_and_execute_script(script_path)
|
||||
|
||||
if success:
|
||||
print(f"\n🎉 Script execution completed successfully")
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
finally:
|
||||
executor.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user