DEV Community

Cover image for Solved: How to automate stuff on a system that can’t run PS scripts?
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: How to automate stuff on a system that can’t run PS scripts?

🚀 Executive Summary

TL;DR: This guide addresses the challenge of automating tasks on Windows systems that cannot run PowerShell scripts due to various limitations. It provides robust alternatives, including leveraging native Windows scripting tools like Batch and VBScript, deploying portable scripting runtimes such as Python and Perl, and implementing diverse orchestration strategies for reliable execution.

🎯 Key Takeaways

  • Native Windows scripting (Batch files and VBScript) provides a baseline for automation on any Windows system, offering capabilities for basic system commands, file operations, service management, and WMI interactions without requiring additional installations.
  • Portable scripting runtimes like Python and Perl can be deployed without administrative privileges (e.g., via ZIP extraction) to overcome PowerShell limitations, offering modern language features, extensive libraries, and cross-platform capabilities for complex automation tasks.
  • Orchestration strategies for non-PowerShell systems include using the local Task Scheduler for scheduled execution, Sysinternals’ PsExec for ad-hoc remote command execution, or building custom Python-based agent/client-server models for robust and secure remote task management.

Navigating automation challenges on systems unable to run PowerShell scripts can be a major roadblock for IT professionals. This guide explores robust alternatives, from native Windows tools to portable scripting and remote orchestration, ensuring your legacy or restricted systems don’t fall behind in automation efforts.

Understanding the Symptoms: Why Can’t I Run PowerShell?

As DevOps engineers, we often encounter diverse environments. While PowerShell is the de-facto standard for Windows automation, many systems pose limitations. Recognizing these “symptoms” is the first step to finding the right solution:

  • Legacy Operating Systems: Older Windows Server versions (e.g., 2003, 2008 R2 without PowerShell 3.0+ updates) may not support modern PowerShell versions or have it installed by default.
  • Security Restrictions: Group Policies or local security policies might explicitly block the execution of powershell.exe or signed scripts, even if the runtime is present.
  • Resource-Constrained Environments: IoT devices, embedded systems, or specialized appliances running stripped-down Windows variants might lack the full PowerShell runtime to minimize footprint.
  • Non-Standard Windows Builds: Custom Windows installations for specific roles might omit components deemed non-essential, including PowerShell.
  • Compliance and Audit Requirements: Certain environments may have strict change control or audit requirements that make introducing new scripting runtimes challenging.

These scenarios often lead to manual, repetitive tasks, increased errors, and a bottleneck in operational efficiency. It’s time to explore alternative automation pathways.

Solution 1: Mastering Native Windows Scripting (Batch & VBScript)

Before introducing anything new, leverage what’s always available: Command Prompt batch files and VBScript. While they may seem archaic, they are powerful for many system-level tasks and require no additional installations.

Batch Files (.cmd/.bat)

Batch files are ideal for basic system commands, file operations, service management, and process control. They execute directly through cmd.exe.

Real-World Example: Automated Log File Cleanup

This batch script cleans up log files older than 30 days and compresses recent logs.

@echo off
set "LOG_DIR=C:\AppLogs"
set "ARCHIVE_DIR=C:\AppArchives\Logs"
set "DAYS_TO_KEEP=30"

echo %DATE% %TIME% - Starting log cleanup for %LOG_DIR%

:: Ensure archive directory exists
if not exist "%ARCHIVE_DIR%" (
    mkdir "%ARCHIVE_DIR%"
    if %ERRORLEVEL% NEQ 0 (
        echo ERROR: Failed to create archive directory %ARCHIVE_DIR%
        goto :EOF
    )
)

:: Delete files older than DAYS_TO_KEEP
echo Deleting log files older than %DAYS_TO_KEEP% days...
FORFILES /P "%LOG_DIR%" /S /M *.log /D -%DAYS_TO_KEEP% /C "cmd /c del @path"
if %ERRORLEVEL% NEQ 0 echo WARNING: Forfiles command had issues deleting old logs.

:: Archive logs older than 7 days but newer than DAYS_TO_KEEP
echo Archiving log files between 7 and %DAYS_TO_KEEP% days old...
FORFILES /P "%LOG_DIR%" /S /M *.log /D -7 /C "cmd /c IF @isdir==FALSE move @path "%ARCHIVE_DIR%""
if %ERRORLEVEL% NEQ 0 echo WARNING: Forfiles command had issues archiving logs.

echo %DATE% %TIME% - Log cleanup completed.
goto :EOF
Enter fullscreen mode Exit fullscreen mode

VBScript (.vbs)

VBScript, executed by wscript.exe or cscript.exe, offers richer capabilities than batch files, especially for interacting with COM objects and Windows Management Instrumentation (WMI). This allows for advanced system queries, service control, user management, and more complex logic.

Real-World Example: Monitor and Restart a Service

This VBScript checks the status of a specific Windows service and restarts it if it’s not running.

Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * from Win32_Service Where Name = 'ServiceName'") ' Replace 'ServiceName'

Dim strServiceName
Dim strServiceStatus

For Each objItem in colItems
    strServiceName = objItem.Name
    strServiceStatus = objItem.State
    Exit For
Next

If strServiceStatus = "Stopped" Then
    WScript.Echo "Service '" & strServiceName & "' is stopped. Attempting to restart..."
    Err.Clear
    On Error Resume Next
    objItem.StartService()
    If Err.Number <> 0 Then
        WScript.Echo "Error restarting service: " & Err.Description
    Else
        WScript.Sleep 5000 ' Wait 5 seconds for service to start
        ' Re-check status
        Set colItems = objWMIService.ExecQuery("Select * from Win32_Service Where Name = '" & strServiceName & "'")
        For Each objItem in colItems
            strServiceStatus = objItem.State
            Exit For
        Next
        If strServiceStatus = "Running" Then
            WScript.Echo "Service '" & strServiceName & "' restarted successfully."
        Else
            WScript.Echo "Service '" & strServiceName & "' is still not running after restart attempt. Current status: " & strServiceStatus
        End If
    End If
ElseIf strServiceStatus = "Running" Then
    WScript.Echo "Service '" & strServiceName & "' is already running."
Else
    WScript.Echo "Service '" & strServiceName & "' status: " & strServiceStatus
End If
Enter fullscreen mode Exit fullscreen mode

Solution 2: Introducing Portable Scripting Runtimes (Python/Perl)

When native scripting hits its limits, bringing in a modern, portable scripting language like Python or Perl can dramatically expand your automation capabilities. The key is “portable” – you don’t need a full system-wide installation, which often requires admin rights or specific registry entries.

Deploying Portable Runtimes

Both Python and Perl can be deployed by simply extracting a ZIP archive to a local directory (e.g., C:\Python or C:\Perl) without needing administrative privileges. You then point your scripts to the executable within that directory.

  • Python: Download the “Windows embeddable package” (zip file) from python.org. Extract it.
  • Perl: ActivePerl or Strawberry Perl offer portable versions or can be installed into a user-local directory.

Python for Advanced Automation

Python’s readability, extensive standard library, and vast ecosystem of third-party modules make it excellent for complex tasks, network interactions, data processing, and API integrations.

Real-World Example: Disk Space Monitoring & Alerting

This Python script checks disk space and could be extended to send an email alert (though that would require a mail client or MAPI integration, or calling an external executable like blat.exe).

# C:\Python\python.exe C:\scripts\check_disk.py
import os
import sys

def get_drive_space(drive):
    """
    Get total, used, and free space for a given drive letter.
    Returns (total_gb, used_gb, free_gb) or None on error.
    """
    if not os.path.exists(drive):
        return None

    try:
        # Use os.statvfs for POSIX-like systems, or fallback to subprocess for Windows
        # For simplicity on older Windows without pywin32, we can call WMIC.
        # This example directly calculates from a subprocess call to WMIC.
        cmd = f'wmic logicaldisk where caption="{drive}" get size,freespace /value'
        process = os.popen(cmd)
        output = process.read()
        process.close()

        size_str = None
        freespace_str = None

        for line in output.splitlines():
            if line.startswith("FreeSpace="):
                freespace_str = line.split("=")[1]
            elif line.startswith("Size="):
                size_str = line.split("=")[1]

        if size_str and freespace_str:
            total_bytes = int(size_str)
            free_bytes = int(freespace_str)
            used_bytes = total_bytes - free_bytes

            # Convert to GB for readability
            total_gb = round(total_bytes / (1024**3), 2)
            used_gb = round(used_bytes / (1024**3), 2)
            free_gb = round(free_bytes / (1024**3), 2)
            return (total_gb, used_gb, free_gb)
        else:
            return None

    except Exception as e:
        print(f"Error getting space for {drive}: {e}", file=sys.stderr)
        return None

if __name__ == "__main__":
    DRIVE_LETTER = "C:" # Or any other drive, e.g., "D:"
    WARNING_THRESHOLD_GB = 10 # Alert if free space below 10GB
    WARNING_THRESHOLD_PERCENT = 15 # Alert if free space below 15%

    space_info = get_drive_space(DRIVE_LETTER)

    if space_info:
        total, used, free = space_info
        free_percent = (free / total) * 100 if total > 0 else 0

        print(f"Drive: {DRIVE_LETTER}")
        print(f"Total Space: {total} GB")
        print(f"Used Space: {used} GB")
        print(f"Free Space: {free} GB ({free_percent:.2f}%)")

        if free <= WARNING_THRESHOLD_GB or free_percent <= WARNING_THRESHOLD_PERCENT:
            print(f"WARNING: Low disk space on {DRIVE_LETTER}!")
            # Add logic here to trigger an alert (e.g., call a batch script to send email)
            sys.exit(1) # Indicate an error for external orchestration
        else:
            print(f"Disk space on {DRIVE_LETTER} is healthy.")
            sys.exit(0)
    else:
        print(f"Could not retrieve disk space for {DRIVE_LETTER}", file=sys.stderr)
        sys.exit(1)
Enter fullscreen mode Exit fullscreen mode

Perl for Text Processing and System Integration

Perl excels at regular expression-based text manipulation and can be highly effective for log parsing, report generation, and interfacing with legacy systems.

Real-World Example: Log File Parser

This Perl script searches a log file for specific error patterns and reports them.

# C:\Perl\bin\perl.exe C:\scripts\parse_app_log.pl
use strict;
use warnings;

my $log_file = "C:\\AppLogs\\application.log"; # Replace with your log file path
my @error_patterns = (
    qr/ERROR:/i,
    qr/FAIL:/i,
    qr/EXCEPTION:/i,
    qr/CRITICAL:/i
);

open my $fh, "<", $log_file or die "Cannot open $log_file: $!";

print "Searching for errors in $log_file...\n";
my $error_found = 0;

while (my $line = <$fh>) {
    foreach my $pattern (@error_patterns) {
        if ($line =~ $pattern) {
            print "Found error: $line";
            $error_found = 1;
            last; # Only report the first matching pattern per line
        }
    }
}

close $fh;

if (!$error_found) {
    print "No errors found.\n";
} else {
    print "Error search completed. Some errors were found.\n";
    exit 1; # Indicate an error for external orchestration
}
Enter fullscreen mode Exit fullscreen mode

Comparison: Native vs. Portable Scripting

Feature Batch/VBScript (Native) Python/Perl (Portable)
Learning Curve Low (Batch), Medium (VBScript) Medium-High, but offers more modern constructs
Ecosystem & Libraries Limited (built-in Windows commands, COM/WMI) Vast (PyPI, CPAN), extensive third-party libraries for almost any task
Cross-Platform Windows-only Highly cross-platform (Windows, Linux, macOS)
Error Handling Basic (ERRORLEVEL, On Error Resume Next) Robust (try-except in Python, eval { } in Perl), specific error modules
Data Structures Primitive (strings, simple arrays in VBScript) Advanced (lists, dictionaries, objects)
External Integrations Primarily COM/WMI, file system, simple network calls HTTP, databases, cloud APIs, network protocols, regex, more
Maintainability Low (spaghetti code common, difficult to refactor) High (readable syntax, modular design, better tooling)
Deployment Always present, no deployment needed Requires runtime deployment (XCOPYable ZIP)

Solution 3: Orchestration & Remote Execution Strategies

Having created robust scripts, the next challenge is to execute them reliably, on schedule, and potentially remotely, without relying on PowerShell-specific remote management.

Local Scheduled Tasks

The fundamental method for automating execution on any Windows system is the Task Scheduler. It can launch any executable or script (batch, VBS, Python, Perl) at specified intervals or in response to events.

Configuration Example: Scheduling a Python Script

You can create scheduled tasks via the GUI (taskschd.msc) or programmatically using SCHTASKS.EXE.

:: Create a scheduled task to run our Python disk check daily at 3 AM
SCHTASKS /CREATE /TN "DiskSpaceMonitor" /TR "C:\Python\python.exe C:\scripts\check_disk.py" /SC DAILY /ST 03:00 /RU System /RL HIGHEST
:: /TN: Task Name
:: /TR: Task Run (the command to execute)
:: /SC: Schedule Type (DAILY, HOURLY, ONCE, etc.)
:: /ST: Start Time
:: /RU: Run As User (System for high privileges, or specific user account)
:: /RL: Run Level (HIGHEST for administrator privileges)
Enter fullscreen mode Exit fullscreen mode

For more complex tasks, wrap your script call in a batch file to handle environment variables or error redirection.

:: C:\scripts\run_disk_check.cmd
@echo off
set "PYTHON_EXE=C:\Python\python.exe"
set "SCRIPT_PATH=C:\scripts\check_disk.py"
set "LOG_FILE=C:\logs\disk_check.log"

"%PYTHON_EXE%" "%SCRIPT_PATH%" >> "%LOG_FILE%" 2>&1

if %ERRORLEVEL% NEQ 0 (
    echo %DATE% %TIME% - Disk check script failed with errorlevel %ERRORLEVEL% >> "%LOG_FILE%"
    :: Potentially trigger an alert here via a VBScript or another tool
) else (
    echo %DATE% %TIME% - Disk check script completed successfully. >> "%LOG_FILE%"
)
Enter fullscreen mode Exit fullscreen mode

Then schedule C:\scripts\run_disk_check.cmd.

PsExec (Sysinternals)

For ad-hoc or simple remote execution of commands and scripts on Windows, Sysinternals' PsExec is a classic and powerful tool. It works by copying a small service executable to the target, executing the command, and then cleaning up. It doesn't rely on PowerShell, only on administrative shares and standard Windows services.

Use Case: Remote Command Execution or Script Triggering

From a jump box or central management server, you can use PsExec to trigger a batch, VBScript, or Python script on the remote system.

:: Execute a local script on a remote machine using PsExec
psexec.exe \\TARGET-HOSTNAME -u Domain\AdminUser -p AdminPassword -h cmd.exe /c "C:\scripts\run_disk_check.cmd"

:: -h: Run with limited token (usually good for security unless specific admin rights are needed)
:: -u, -p: Credentials for connecting to the remote machine
:: cmd.exe /c: Executes the command and then exits
Enter fullscreen mode Exit fullscreen mode

Security Note: Passing credentials directly on the command line is generally discouraged. Consider using credential management systems or other secure methods in production environments.

Simple Agent/Client-Server Model (Custom Python Service)

For more advanced, robust, and custom remote execution scenarios where off-the-shelf RMM tools are not an option, you can build a lightweight agent using Python. This agent runs as a Windows Service on the target system.

Concept:

  • A small Python script (the "agent") is installed as a Windows Service (e.g., using pywin32 or a tool like NSSM - Non-Sucking Service Manager).
  • This agent periodically polls a central repository (e.g., a shared network folder, a simple HTTP API on a central server, or a database) for tasks.
  • When a task is found (e.g., "run C:\scripts\update_app.cmd"), the agent executes it locally using subprocess.run().
  • The agent reports back the execution status and output to the central repository.

This approach provides a highly customizable and secure way to manage automation across systems that cannot run PowerShell, giving you full control over the communication and execution logic.

Agent Stub (Illustrative - requires NSSM or pywin32 for service creation)

# C:\scripts\simple_agent.py
import time
import subprocess
import os
import sys

# Configuration
TASK_QUEUE_DIR = "\\\\central-server\\share\\task_queue" # Network path for tasks
REPORT_DIR = "\\\\central-server\\share\\reports"    # Network path for reports
POLLING_INTERVAL = 60 # seconds

def execute_task(task_file):
    print(f"Executing task: {task_file}")
    try:
        # Read command from task file
        with open(task_file, 'r') as f:
            command = f.read().strip()

        print(f"Command: {command}")
        # Execute the command
        result = subprocess.run(command, shell=True, capture_output=True, text=True)

        report_filename = os.path.basename(task_file).replace(".task", ".report")
        report_path = os.path.join(REPORT_DIR, report_filename)

        with open(report_path, 'w') as f:
            f.write(f"--- Task: {command} ---\n")
            f.write(f"Return Code: {result.returncode}\n")
            f.write(f"STDOUT:\n{result.stdout}\n")
            f.write(f"STDERR:\n{result.stderr}\n")
        print(f"Report written to {report_path}")

        # Delete the task file after execution
        os.remove(task_file)

    except Exception as e:
        print(f"Error executing task {task_file}: {e}", file=sys.stderr)
        error_report_filename = os.path.basename(task_file).replace(".task", ".error")
        error_report_path = os.path.join(REPORT_DIR, error_report_filename)
        with open(error_report_path, 'w') as f:
            f.write(f"Error processing {task_file}: {e}\n")
            f.write(f"Python traceback:\n{sys.exc_info()}\n")
        if os.path.exists(task_file):
            os.remove(task_file) # Still try to remove the task file

def main_loop():
    print("Agent started. Polling for tasks...")
    while True:
        try:
            for filename in os.listdir(TASK_QUEUE_DIR):
                if filename.endswith(".task"):
                    task_path = os.path.join(TASK_QUEUE_DIR, filename)
                    execute_task(task_path)
            time.sleep(POLLING_INTERVAL)
        except Exception as e:
            print(f"Agent experienced an error in main loop: {e}", file=sys.stderr)
            time.sleep(POLLING_INTERVAL * 2) # Longer sleep on error

if __name__ == "__main__":
    main_loop()
Enter fullscreen mode Exit fullscreen mode

To turn this into a service, you would use NSSM (Non-Sucking Service Manager) to wrap this Python script and manage its lifecycle as a Windows service.

Conclusion

Automating systems that cannot run PowerShell scripts requires a strategic approach, but it's far from impossible. By understanding the capabilities of native Windows tools, embracing portable scripting runtimes, and implementing robust orchestration strategies, you can maintain efficient, error-free operations across your entire infrastructure. Choose the solution that best fits your system's constraints, security policies, and your team's expertise.


Darian Vance

👉 Read the original article on TechResolve.blog

Top comments (0)