Skip to main content
Glama
server.py54.5 kB
import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s" ) """ MCP Server for secure PowerShell command execution. """ import re import sys import asyncio import subprocess from typing import Optional from mcp.server.fastmcp import FastMCP, Context import os import json from datetime import datetime from typing import Dict, Any, List, Optional, Union from contextlib import asynccontextmanager from collections.abc import AsyncIterator # Create an MCP server with better metadata and dependencies mcp = FastMCP( "PowerShell Integration Server", description="Secure PowerShell command execution and script generation for Windows system administration, including Intune and BigFix deployment scripts", dependencies=["asyncio", "psutil>=5.9.0"], capabilities={ "tools": True, "resources": True, "resourceTemplates": True, "prompts": True } ) TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), 'templates') def format_json_output(code: str) -> str: """Add JSON formatting to PowerShell code if not present.""" if not code.strip().lower().endswith('| convertto-json'): code = f"{code} | ConvertTo-Json" return code def validate_powershell_code(code: str) -> bool: """ Validate PowerShell code for potentially harmful commands. Args: code: The PowerShell code to validate Returns: bool: True if code passes validation """ dangerous_patterns = [ r"rm\s+(-r|-f|/s)*\s*/", # Dangerous recursive deletes r"format\s+[a-z]:", # Drive formatting r"Stop-Computer", # System shutdown r"Restart-Computer", # System restart r"Remove-Item.*-Recurse", # Recursive deletion r"Invoke-Expression", # Dynamic code execution r"iex", # Alias for Invoke-Expression r"Start-Process", # Starting new processes r"New-Service", # Creating services r"Set-Service", # Modifying services r"net\s+user", # User account manipulation ] return not any(re.search(pattern, code, re.IGNORECASE) for pattern in dangerous_patterns) @mcp.tool() async def run_powershell(code: str, timeout: Optional[int] = 60, ctx: Optional[Context] = None) -> str: """Execute PowerShell commands securely. Args: code: PowerShell code to execute timeout: Command timeout in seconds (1-300, default 60) ctx: MCP context for logging and progress reporting Returns: Command output as string """ if ctx: await ctx.info(f"Executing PowerShell command (timeout: {timeout}s)") return await execute_powershell(code, timeout, ctx) @mcp.tool() async def get_system_info(properties: Optional[List[str]] = None, timeout: Optional[int] = 60) -> str: """Get system information. Args: properties: List of ComputerInfo properties to retrieve (optional) timeout: Command timeout in seconds (1-300, default 60) """ code = "Get-ComputerInfo" if properties: properties_str = ",".join(properties) code = f"{code} -Property {properties_str}" return await execute_powershell(format_json_output(code), timeout) @mcp.tool() async def get_running_services(name: Optional[str] = None, status: Optional[str] = None, timeout: Optional[int] = 60) -> str: """Get information about running services. Args: name: Filter services by name (supports wildcards) status: Filter by status (Running, Stopped, etc.) timeout: Command timeout in seconds (1-300, default 60) """ code = "Get-Service" filters = [] if name: filters.append(f"Name -like '{name}'") if status: filters.append(f"Status -eq '{status}'") if filters: code = f"{code} | Where-Object {{ {' -and '.join(filters)} }}" code = f"{code} | Select-Object Name, DisplayName, Status, StartType" return await execute_powershell(format_json_output(code), timeout) @mcp.tool() async def get_processes(name: Optional[str] = None, top: Optional[int] = None, sort_by: Optional[str] = None, timeout: Optional[int] = 60) -> str: """Get information about running processes. Args: name: Filter processes by name (supports wildcards) top: Limit to top N processes sort_by: Property to sort by (e.g., CPU, WorkingSet) timeout: Command timeout in seconds (1-300, default 60) """ code = "Get-Process" if name: code = f"{code} -Name '{name}'" if sort_by: code = f"{code} | Sort-Object -Property {sort_by} -Descending" if top: code = f"{code} | Select-Object -First {top}" code = f"{code} | Select-Object Name, Id, CPU, WorkingSet, StartTime" return await execute_powershell(format_json_output(code), timeout) @mcp.tool() async def get_event_logs(logname: str, newest: Optional[int] = 10, level: Optional[int] = None, timeout: Optional[int] = 60) -> str: """Get Windows event logs. Args: logname: Name of the event log (System, Application, Security, etc.) newest: Number of most recent events to retrieve (default 10) level: Filter by event level (1: Critical, 2: Error, 3: Warning, 4: Information) timeout: Command timeout in seconds (1-300, default 60) """ code = f"Get-EventLog -LogName {logname} -Newest {newest}" if level: code = f"{code} | Where-Object {{ $_.EntryType -eq {level} }}" code = f"{code} | Select-Object TimeGenerated, EntryType, Source, Message" return await execute_powershell(format_json_output(code), timeout) @mcp.tool() async def generate_script_from_template( template_name: str, parameters: Dict[str, Any], output_path: Optional[str] = None, timeout: Optional[int] = 60 ) -> str: """Generate a PowerShell script from a template. Args: template_name: Name of the template to use (without .ps1 extension) parameters: Dictionary of parameters to replace in the template output_path: Where to save the generated script (optional) timeout: Command timeout in seconds (1-300, default 60) Returns: Generated script content or path where script was saved """ template_path = os.path.join(TEMPLATES_DIR, f"{template_name}.ps1") if not os.path.exists(template_path): raise ValueError(f"Template {template_name} not found") with open(template_path, 'r') as f: template_content = f.read() # Replace template variables script_content = template_content parameters['DATE'] = datetime.now().strftime('%Y-%m-%d') for key, value in parameters.items(): script_content = script_content.replace(f"{{{{{key}}}}}", str(value)) if output_path: with open(output_path, 'w') as f: f.write(script_content) return f"Script generated and saved to: {output_path}" return script_content @mcp.tool() async def generate_custom_script( description: str, script_type: str, parameters: Optional[List[Dict[str, Any]]] = None, include_logging: bool = True, include_error_handling: bool = True, output_path: Optional[str] = None, timeout: Optional[int] = 60 ) -> str: """Generate a custom PowerShell script based on description. Args: description: Natural language description of what the script should do script_type: Type of script to generate (file_ops, service_mgmt, etc.) parameters: List of parameters the script should accept include_logging: Whether to include logging functions include_error_handling: Whether to include error handling output_path: Where to save the generated script (optional) timeout: Command timeout in seconds (1-300, default 60) Returns: Generated script content or path where script was saved """ script_content = [] # Add help comment block script_content.extend([ '<#', '.SYNOPSIS', f' {description}', '.DESCRIPTION', f' Dynamically generated PowerShell script for {script_type}', '.NOTES', ' Generated by PowerShell MCP Server', f' Date: {datetime.now().strftime("%Y-%m-%d")}', '#>' ]) # Add parameters if parameters: script_content.append('\nparam (') for param in parameters: param_str = f' [Parameter(Mandatory=${param.get("mandatory", "false")})]' if param.get('type'): param_str += f'\n [{param["type"]}]' param_str += f'${param["name"]}' if param.get('default'): param_str += f' = "{param["default"]}"' script_content.append(param_str + ',') script_content[-1] = script_content[-1].rstrip(',') # Remove trailing comma script_content.append(')') # Add logging function if include_logging: script_content.extend([ '\n# Function to write log messages', 'function Write-Log {', ' param (', ' [Parameter(Mandatory=$true)]', ' [string]$Message,', ' [Parameter(Mandatory=$false)]', ' [ValidateSet("INFO", "WARNING", "ERROR")]', ' [string]$Level = "INFO"', ' )', ' $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"', ' Write-Host "[$timestamp] [$Level] $Message"', '}' ]) # Add error handling if include_error_handling: script_content.extend([ '\n# Function to handle errors', 'function Handle-Error {', ' param (', ' [Parameter(Mandatory=$true)]', ' [System.Management.Automation.ErrorRecord]$ErrorRecord', ' )', ' Write-Log -Level ERROR -Message "Error occurred: $($ErrorRecord.Exception.Message)"', ' Write-Log -Level ERROR -Message "Error details: $($ErrorRecord | Out-String)"', '}' ]) # Add main script block script_content.extend([ '\n# Main execution', 'try {', ' Write-Log "Starting script execution..."', ' ', ' # TODO: Add script logic here based on description', ' # This is where you would add the specific PowerShell commands', ' Write-Log "Script completed successfully."', '}', 'catch {', ' Handle-Error -ErrorRecord $_', ' exit 1', '}' ]) final_content = '\n'.join(script_content) if output_path: with open(output_path, 'w') as f: f.write(final_content) return f"Script generated and saved to: {output_path}" return final_content def normalize_path(path: str) -> str: """Convert relative paths to absolute using current working directory.""" if not path: raise ValueError("Path cannot be empty") if path.startswith(('./','.\\')): path = path[2:] if not os.path.isabs(path): path = os.path.join(os.getcwd(), path) return os.path.abspath(path) @mcp.tool() def ensure_directory(path: str) -> str: """Ensure directory exists and return absolute path.""" abs_path = normalize_path(path) if os.path.splitext(abs_path)[1]: # If path has an extension dir_path = os.path.dirname(abs_path) else: dir_path = abs_path os.makedirs(dir_path, exist_ok=True) return abs_path async def generate_intune_detection_script( description: str, detection_logic: str, output_path: Optional[str] = None, timeout: Optional[int] = 60 ) -> str: """Generate a Microsoft Intune detection script with enterprise-grade compliance checking. Creates a PowerShell detection script that follows Microsoft Intune best practices: - Proper exit codes (0=compliant, 1=non-compliant, 2=error) - Event log integration for monitoring and troubleshooting - Fast execution optimized for frequent compliance checks - Comprehensive error handling and logging - No user interaction (required for Intune deployment) 💡 TIP: For complete Intune compliance, you need BOTH detection and remediation scripts. Consider using 'generate_intune_script_pair' to create both scripts together with matching logic. Microsoft References: - Intune Detection Scripts: https://docs.microsoft.com/en-us/mem/intune/fundamentals/remediations - Best Practices: https://docs.microsoft.com/en-us/mem/intune/fundamentals/remediations-script-samples - PowerShell Requirements: https://docs.microsoft.com/en-us/mem/intune/apps/intune-management-extension - Exit Code Standards: https://docs.microsoft.com/en-us/mem/intune/apps/troubleshoot-mam-app-deployment Args: description: Clear description of what the script should detect (e.g., 'Check if Chrome is installed with correct version', 'Verify Windows firewall is enabled') detection_logic: PowerShell code that performs the compliance check. Use 'Complete-Detection -Compliant $true/$false -Message "status"' to indicate result output_path: Optional file path where the script will be saved. If not provided, returns script content timeout: Command timeout in seconds (1-300, default 60) Returns: Generated script content or path where script was saved Example: Generate a script to detect Chrome installation: ``` result = await generate_intune_detection_script( description="Check if Chrome browser is installed with version 100.0.0.0 or higher", detection_logic=''' try { $app = Get-ItemProperty "HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe" -ErrorAction Stop $version = (Get-Item $app.'(Default)').VersionInfo.FileVersion $compliant = [version]$version -ge [version]"100.0.0.0" Complete-Detection -Compliant $compliant -Message "Chrome version: $version (Required: 100.0.0.0+)" } catch { Complete-Detection -Compliant $false -Message "Chrome not found or inaccessible" } ''', output_path="detect_chrome.ps1" ) ``` Tips: - Keep detection logic fast and efficient (runs frequently) - Always use Complete-Detection function to set proper exit codes - Use try-catch blocks for robust error handling - Test detection logic thoroughly in different environments - Use Write-IntuneLog for detailed progress tracking - Avoid making changes in detection scripts (read-only operations) """ params = { "SYNOPSIS": f"Intune Detection Script - {description}", "DESCRIPTION": description, "DATE": datetime.now().strftime('%Y-%m-%d'), "DETECTION_LOGIC": detection_logic } if output_path: output_path = ensure_directory(output_path) return await generate_script_from_template("intune_detection", params, output_path, timeout) @mcp.tool() async def generate_intune_remediation_script( description: str, remediation_logic: str, output_path: Optional[str] = None, timeout: Optional[int] = 60 ) -> str: """Generate a Microsoft Intune remediation script with enterprise-grade features. Creates a PowerShell remediation script that follows Microsoft Intune best practices: - Proper exit codes (0=success, 1=failure, 2=error) - Event log integration for monitoring and troubleshooting - System restore point creation before making changes - Comprehensive error handling and logging - No user interaction (required for Intune deployment) ⚠️ IMPORTANT: For complete Intune compliance, you need BOTH detection and remediation scripts. Consider using 'generate_intune_script_pair' instead to create both scripts together. Microsoft References: - Intune Remediation Scripts: https://docs.microsoft.com/en-us/mem/intune/fundamentals/remediations - Best Practices: https://docs.microsoft.com/en-us/mem/intune/fundamentals/remediations-script-samples - PowerShell Script Requirements: https://docs.microsoft.com/en-us/mem/intune/apps/intune-management-extension - Exit Code Standards: https://docs.microsoft.com/en-us/mem/intune/apps/troubleshoot-mam-app-installation#exit-codes Args: description: Clear description of what the script should remediate (e.g., 'Install Chrome browser', 'Configure Windows firewall') remediation_logic: PowerShell code that performs the remediation. Use 'Complete-Remediation -Success $true -Message "description"' to indicate completion output_path: Optional file path where the script will be saved. If not provided, returns script content timeout: Command timeout in seconds (1-300, default 60) Returns: Generated script content or path where script was saved Example: Generate a script to install Chrome: ``` result = await generate_intune_remediation_script( description="Install Chrome browser to latest version", remediation_logic=''' $installer = "$env:TEMP\\ChromeSetup.exe" Invoke-WebRequest -Uri "https://dl.google.com/chrome/install/latest/chrome_installer.exe" -OutFile $installer Start-Process -FilePath $installer -Args "/silent /install" -Wait Remove-Item $installer -Force Complete-Remediation -Success $true -Message "Chrome installation completed successfully" ''', output_path="remediate_chrome.ps1" ) ``` Tips: - Always use Complete-Remediation function to set proper exit codes - Test your remediation_logic in a safe environment first - Consider creating a system restore point for major changes - Use Write-IntuneLog for detailed logging and troubleshooting - Ensure no user interaction is required (scripts run silently) """ params = { "SYNOPSIS": f"Intune Remediation Script - {description}", "DESCRIPTION": description, "DATE": datetime.now().strftime('%Y-%m-%d'), "REMEDIATION_LOGIC": remediation_logic } if output_path: output_path = ensure_directory(output_path) return await generate_script_from_template("intune_remediation", params, output_path, timeout) @mcp.tool() async def generate_intune_script_pair( description: str, detection_logic: str, remediation_logic: str, output_dir: Optional[str] = None, timeout: Optional[int] = 60 ) -> Dict[str, str]: """Generate a complete pair of Microsoft Intune detection and remediation scripts. This is the RECOMMENDED tool for Intune compliance as it creates both required scripts: - Detection script: Checks current system state and determines compliance - Remediation script: Fixes non-compliant conditions with proper safeguards Both scripts follow Microsoft Intune best practices: - Proper exit codes (Detection: 0=compliant, 1=non-compliant, 2=error; Remediation: 0=success, 1=failure, 2=error) - Event log integration for centralized monitoring - System restore points before changes (remediation only) - Comprehensive error handling and logging - No user interaction (silent execution required) Microsoft References: - Intune Remediation Scripts Overview: https://docs.microsoft.com/en-us/mem/intune/fundamentals/remediations - Script Deployment Best Practices: https://docs.microsoft.com/en-us/mem/intune/fundamentals/remediations-script-samples - PowerShell Requirements: https://docs.microsoft.com/en-us/mem/intune/apps/intune-management-extension - Exit Code Standards: https://docs.microsoft.com/en-us/mem/intune/apps/troubleshoot-mam-app-deployment - Monitoring and Reporting: https://docs.microsoft.com/en-us/mem/intune/fundamentals/remediations-monitor Args: description: Clear description of what the scripts should detect and remediate (e.g., 'Ensure Chrome browser is installed with latest version') detection_logic: PowerShell code that performs the compliance check. Use 'Complete-Detection -Compliant $true/$false -Message "status"' to indicate result remediation_logic: PowerShell code that fixes non-compliant conditions. Use 'Complete-Remediation -Success $true/$false -Message "result"' to indicate completion output_dir: Optional directory to save both scripts. If not provided, returns script content in response timeout: Command timeout in seconds (1-300, default 60) Returns: Dictionary containing both scripts: {"detection_script": "content/path", "remediation_script": "content/path"} Example: Generate scripts to manage Chrome browser installation: ``` result = await generate_intune_script_pair( description="Ensure Chrome browser is installed with version 100.0.0.0 or higher", detection_logic=''' try { $app = Get-ItemProperty "HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe" -ErrorAction Stop $version = (Get-Item $app.'(Default)').VersionInfo.FileVersion $compliant = [version]$version -ge [version]"100.0.0.0" Complete-Detection -Compliant $compliant -Message "Chrome version: $version (Required: 100.0.0.0+)" } catch { Complete-Detection -Compliant $false -Message "Chrome not found or inaccessible" } ''', remediation_logic=''' try { $installer = "$env:TEMP\\ChromeSetup.exe" Write-IntuneLog "Downloading Chrome installer..." Invoke-WebRequest -Uri "https://dl.google.com/chrome/install/latest/chrome_installer.exe" -OutFile $installer -UseBasicParsing Write-IntuneLog "Installing Chrome silently..." Start-Process -FilePath $installer -Args "/silent /install" -Wait Remove-Item $installer -Force Complete-Remediation -Success $true -Message "Chrome installation completed successfully" } catch { Complete-Remediation -Success $false -Message "Chrome installation failed: $($_.Exception.Message)" } ''', output_dir="chrome_intune_scripts" ) ``` Tips: - Always test both scripts in a controlled environment first - Use descriptive logging messages for easier troubleshooting - Consider the impact of remediation actions (e.g., system restarts, user disruption) - Use Write-IntuneLog for detailed progress tracking - Ensure detection logic is fast and efficient (runs frequently) - Make remediation logic idempotent (safe to run multiple times) """ if output_dir: # Create output directory in current working directory abs_output_dir = ensure_directory(output_dir) # Create full paths for scripts detect_path = os.path.join(abs_output_dir, "detect.ps1") remedy_path = os.path.join(abs_output_dir, "remedy.ps1") # Create parent directory if it doesn't exist os.makedirs(abs_output_dir, exist_ok=True) detect_result = await generate_intune_detection_script( description=description, detection_logic=detection_logic, output_path=detect_path, timeout=timeout ) remedy_result = await generate_intune_remediation_script( description=description, remediation_logic=remediation_logic, output_path=remedy_path, timeout=timeout ) else: detect_result = await generate_intune_detection_script( description=description, detection_logic=detection_logic, timeout=timeout ) remedy_result = await generate_intune_remediation_script( description=description, remediation_logic=remediation_logic, timeout=timeout ) return { "detection_script": detect_result, "remediation_script": remedy_result } @mcp.tool() async def generate_bigfix_relevance_script( description: str, relevance_logic: str, output_path: Optional[str] = None, timeout: Optional[int] = 60 ) -> str: """Generate a BigFix relevance script to determine if computers need action. Creates a PowerShell relevance script that follows IBM BigFix best practices: - Proper output format (TRUE/FALSE for BigFix consumption) - BigFix client log integration for monitoring - Event log integration for troubleshooting - Comprehensive error handling and logging - Fast execution optimized for frequent evaluations 💡 TIP: For complete BigFix deployments, you need BOTH relevance and action scripts. Consider using 'generate_bigfix_script_pair' to create both scripts together with matching logic. IBM BigFix References: - Relevance Language Guide: https://help.hcltechsw.com/bigfix/11.0/relevance/Relevance/c_relevance_language.html - Action Scripts: https://help.hcltechsw.com/bigfix/11.0/platform/Platform/Console/c_creating_action_scripts.html - Best Practices: https://help.hcltechsw.com/bigfix/11.0/platform/Platform/Console/c_best_practices_for_creating_fixlets.html - Client Logging: https://help.hcltechsw.com/bigfix/11.0/platform/Platform/Installation/c_bes_client_logging.html Args: description: Clear description of what the script should check (e.g., 'Check if Chrome needs updating', 'Verify Windows patches are current') relevance_logic: PowerShell code that determines relevance. Use 'Complete-Relevance -Relevant $true/$false -Message "status"' to indicate result output_path: Optional file path where the script will be saved. If not provided, returns script content timeout: Command timeout in seconds (1-300, default 60) Returns: Generated script content or path where script was saved Example: Generate a script to check if Chrome needs updating: ``` result = await generate_bigfix_relevance_script( description="Check if Chrome browser needs updating to version 100.0.0.0 or higher", relevance_logic=''', try { $app = Get-ItemProperty "HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe" -ErrorAction Stop $version = (Get-Item $app.'(Default)').VersionInfo.FileVersion $needsUpdate = [version]$version -lt [version]"100.0.0.0" Complete-Relevance -Relevant $needsUpdate -Message "Chrome version: $version (Target: 100.0.0.0+)" } catch { Complete-Relevance -Relevant $true -Message "Chrome not found or inaccessible - installation needed" } ''', output_path="chrome_relevance.ps1" ) ``` Tips: - Keep relevance logic fast and efficient (evaluated frequently) - Return TRUE when action is needed, FALSE when compliant - Always use Complete-Relevance function for proper BigFix output format - Use try-catch blocks for robust error handling - Test relevance logic thoroughly across different environments - Use Write-BigFixLog for detailed progress tracking """ params = { "SYNOPSIS": f"BigFix Relevance Script - {description}", "DESCRIPTION": description, "DATE": datetime.now().strftime('%Y-%m-%d'), "RELEVANCE_LOGIC": relevance_logic } if output_path: output_path = ensure_directory(output_path) return await generate_script_from_template("bigfix_relevance", params, output_path, timeout) @mcp.tool() async def generate_bigfix_action_script( description: str, action_logic: str, output_path: Optional[str] = None, timeout: Optional[int] = 60 ) -> str: """Generate a BigFix action script to perform remediation or configuration changes. Creates a PowerShell action script that follows IBM BigFix best practices: - Proper exit codes (0=success, 1=retryable failure, 2=non-retryable failure) - BigFix client log integration for monitoring - System restore point creation before changes - Comprehensive error handling and logging - Event log integration for troubleshooting ⚠️ IMPORTANT: For complete BigFix deployments, you need BOTH relevance and action scripts. Consider using 'generate_bigfix_script_pair' instead to create both scripts together. IBM BigFix References: - Action Scripts: https://help.hcltechsw.com/bigfix/11.0/platform/Platform/Console/c_creating_action_scripts.html - Exit Codes: https://help.hcltechsw.com/bigfix/11.0/platform/Platform/Console/c_action_script_exit_codes.html - Best Practices: https://help.hcltechsw.com/bigfix/11.0/platform/Platform/Console/c_best_practices_for_creating_fixlets.html - Client Logging: https://help.hcltechsw.com/bigfix/11.0/platform/Platform/Installation/c_bes_client_logging.html Args: description: Clear description of what the script should accomplish (e.g., 'Install Chrome browser', 'Configure Windows firewall') action_logic: PowerShell code that performs the action. Use 'Complete-Action -Result "Success/RetryableFailure/NonRetryableFailure" -Message "details"' to indicate completion output_path: Optional file path where the script will be saved. If not provided, returns script content timeout: Command timeout in seconds (1-300, default 60) Returns: Generated script content or path where script was saved Example: Generate a script to install Chrome: ``` result = await generate_bigfix_action_script( description="Install Chrome browser to latest version", action_logic=''' try { $installer = "$env:TEMP\\ChromeSetup.exe" Write-BigFixLog "Downloading Chrome installer..." Invoke-WebRequest -Uri "https://dl.google.com/chrome/install/latest/chrome_installer.exe" -OutFile $installer -UseBasicParsing Write-BigFixLog "Installing Chrome silently..." Start-Process -FilePath $installer -Args "/silent /install" -Wait Remove-Item $installer -Force Complete-Action -Result "Success" -Message "Chrome installation completed successfully" } catch { Complete-Action -Result "RetryableFailure" -Message "Chrome installation failed: $($_.Exception.Message)" } ''', output_path="chrome_action.ps1" ) ``` Tips: - Always use Complete-Action function to set proper exit codes - Use "Success" for completed actions - Use "RetryableFailure" for temporary issues (network, locks, etc.) - Use "NonRetryableFailure" for permanent issues (unsupported OS, etc.) - Test action logic in safe environments first - Consider creating system restore points for major changes - Use Write-BigFixLog for detailed logging and troubleshooting - Make actions idempotent (safe to run multiple times) """ params = { "SYNOPSIS": f"BigFix Action Script - {description}", "DESCRIPTION": description, "DATE": datetime.now().strftime('%Y-%m-%d'), "ACTION_LOGIC": action_logic } if output_path: output_path = ensure_directory(output_path) return await generate_script_from_template("bigfix_action", params, output_path, timeout) @mcp.tool() async def generate_bigfix_script_pair( description: str, relevance_logic: str, action_logic: str, output_dir: Optional[str] = None, timeout: Optional[int] = 60 ) -> Dict[str, str]: """Generate a complete pair of BigFix relevance and action scripts for deployment. This is the RECOMMENDED tool for BigFix fixlet creation as it creates both required scripts: - Relevance script: Determines which computers need the action (TRUE/FALSE output) - Action script: Performs the necessary changes with proper error handling Both scripts follow IBM BigFix best practices: - Proper BigFix output formats and exit codes - BigFix client log integration for centralized monitoring - System restore points before changes (action only) - Comprehensive error handling and logging - Event log integration for troubleshooting - No user interaction (silent execution required) IBM BigFix References: - Fixlet Development: https://help.hcltechsw.com/bigfix/11.0/platform/Platform/Console/c_creating_fixlets.html - Relevance Language: https://help.hcltechsw.com/bigfix/11.0/relevance/Relevance/c_relevance_language.html - Action Scripts: https://help.hcltechsw.com/bigfix/11.0/platform/Platform/Console/c_creating_action_scripts.html - Best Practices: https://help.hcltechsw.com/bigfix/11.0/platform/Platform/Console/c_best_practices_for_creating_fixlets.html - Testing Guidelines: https://help.hcltechsw.com/bigfix/11.0/platform/Platform/Console/c_testing_fixlets.html Args: description: Clear description of what the scripts should accomplish (e.g., 'Manage Chrome browser installation and updates') relevance_logic: PowerShell code that determines if action is needed. Use 'Complete-Relevance -Relevant $true/$false -Message "status"' to indicate result action_logic: PowerShell code that performs the remediation. Use 'Complete-Action -Result "Success/RetryableFailure/NonRetryableFailure" -Message "details"' to indicate completion output_dir: Optional directory to save both scripts. If not provided, returns script content in response timeout: Command timeout in seconds (1-300, default 60) Returns: Dictionary containing both scripts: {"relevance_script": "content/path", "action_script": "content/path"} Example: Generate scripts to manage Chrome browser installation: ``` result = await generate_bigfix_script_pair( description="Manage Chrome browser installation with version 100.0.0.0 or higher", relevance_logic=''', try { $app = Get-ItemProperty "HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe" -ErrorAction Stop $version = (Get-Item $app.'(Default)').VersionInfo.FileVersion $needsAction = [version]$version -lt [version]"100.0.0.0" Complete-Relevance -Relevant $needsAction -Message "Chrome version: $version (Target: 100.0.0.0+)" } catch { Complete-Relevance -Relevant $true -Message "Chrome not found - installation needed" } ''', action_logic=''', try { $installer = "$env:TEMP\\ChromeSetup.exe" Write-BigFixLog "Downloading Chrome installer..." Invoke-WebRequest -Uri "https://dl.google.com/chrome/install/latest/chrome_installer.exe" -OutFile $installer -UseBasicParsing Write-BigFixLog "Installing Chrome silently..." Start-Process -FilePath $installer -Args "/silent /install" -Wait Remove-Item $installer -Force Complete-Action -Result "Success" -Message "Chrome installation completed successfully" } catch { Complete-Action -Result "RetryableFailure" -Message "Chrome installation failed: $($_.Exception.Message)" } ''', output_dir="chrome_bigfix_scripts" ) ``` Tips: - Always test both scripts in a controlled environment first - Ensure relevance logic matches the conditions that action script addresses - Use descriptive logging messages for easier troubleshooting - Consider the scope and impact of actions (test groups first) - Make sure relevance logic is efficient (evaluated frequently) - Ensure action logic is idempotent (safe to run multiple times) - Use Write-BigFixLog for detailed progress tracking - Test across different OS versions and configurations """ if output_dir: # Create output directory in current working directory abs_output_dir = ensure_directory(output_dir) # Create full paths for scripts relevance_path = os.path.join(abs_output_dir, "relevance.ps1") action_path = os.path.join(abs_output_dir, "action.ps1") # Create parent directory if it doesn't exist os.makedirs(abs_output_dir, exist_ok=True) relevance_result = await generate_bigfix_relevance_script( description=description, relevance_logic=relevance_logic, output_path=relevance_path, timeout=timeout ) action_result = await generate_bigfix_action_script( description=description, action_logic=action_logic, output_path=action_path, timeout=timeout ) else: relevance_result = await generate_bigfix_relevance_script( description=description, relevance_logic=relevance_logic, timeout=timeout ) action_result = await generate_bigfix_action_script( description=description, action_logic=action_logic, timeout=timeout ) return { "relevance_script": relevance_result, "action_script": action_result } async def execute_powershell(code: str, timeout: Optional[int] = 60, ctx: Optional[Context] = None) -> str: """Execute PowerShell commands securely. Args: code: PowerShell code to execute timeout: Command timeout in seconds (1-300, default 60) ctx: MCP context for logging and progress reporting Returns: Command output as string """ # Validate timeout if not isinstance(timeout, int) or timeout < 1 or timeout > 300: raise ValueError("timeout must be between 1 and 300 seconds") # Validate code if not validate_powershell_code(code): raise ValueError("PowerShell code contains potentially dangerous commands") if ctx: await ctx.info("Validating PowerShell code...") # Create and run process if ctx: await ctx.info("Starting PowerShell process...") process = await asyncio.create_subprocess_exec( "powershell", "-NoProfile", # Don't load profiles "-NonInteractive", # No interactive prompts "-Command", code, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) try: if ctx: await ctx.info("Executing command...") stdout, stderr = await asyncio.wait_for( process.communicate(), timeout=timeout ) except asyncio.TimeoutError: process.kill() if ctx: await ctx.error(f"Command timed out after {timeout} seconds") raise TimeoutError(f"Command timed out after {timeout} seconds") if process.returncode != 0: error_msg = stderr.decode() if stderr else "Command failed with no error output" if ctx: await ctx.error(f"PowerShell command failed: {error_msg}") raise RuntimeError(error_msg) result = stdout.decode() if stdout else "" if ctx: await ctx.info(f"Command completed successfully, returned {len(result)} characters") return result @mcp.tool() async def run_powershell_with_progress( code: str, timeout: Optional[int] = 60, ctx: Optional[Context] = None ) -> str: """Execute PowerShell commands with detailed progress reporting. Args: code: PowerShell code to execute timeout: Command timeout in seconds (1-300, default 60) ctx: MCP context for logging and progress reporting Returns: Command output as string with execution details """ if not ctx: # If no context provided, fall back to basic execution return await execute_powershell(code, timeout) start_time = datetime.now() try: await ctx.info("🔍 Validating PowerShell code...") # Validate timeout if not isinstance(timeout, int) or timeout < 1 or timeout > 300: await ctx.error("❌ Invalid timeout value") raise ValueError("timeout must be between 1 and 300 seconds") # Validate code if not validate_powershell_code(code): await ctx.error("❌ PowerShell code contains potentially dangerous commands") raise ValueError("PowerShell code contains potentially dangerous commands") await ctx.info("✅ Code validation passed") await ctx.info("🚀 Starting PowerShell execution...") # Report progress at start await ctx.report_progress(0, 4, "Initializing PowerShell process") # Create and run process process = await asyncio.create_subprocess_exec( "powershell", "-NoProfile", "-NonInteractive", "-Command", code, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) await ctx.report_progress(1, 4, "Process created, executing command") try: stdout, stderr = await asyncio.wait_for( process.communicate(), timeout=timeout ) await ctx.report_progress(3, 4, "Command execution completed") except asyncio.TimeoutError: process.kill() await ctx.error(f"⏰ Command timed out after {timeout} seconds") raise TimeoutError(f"Command timed out after {timeout} seconds") if process.returncode != 0: error_msg = stderr.decode() if stderr else "Command failed with no error output" await ctx.error(f"❌ PowerShell command failed: {error_msg}") raise RuntimeError(error_msg) result = stdout.decode() if stdout else "" # Calculate execution time end_time = datetime.now() execution_time = (end_time - start_time).total_seconds() await ctx.report_progress(4, 4, "Processing results") await ctx.info(f"✅ Command completed successfully in {execution_time:.2f} seconds") await ctx.info(f"📊 Output size: {len(result)} characters") # Add execution metadata to result metadata = { "execution_time_seconds": execution_time, "output_length": len(result), "exit_code": process.returncode, "timestamp": end_time.isoformat() } if result.strip(): return f"--- PowerShell Output ---\n{result}\n--- Execution Metadata ---\n{json.dumps(metadata, indent=2)}" else: return f"--- No Output Produced ---\n--- Execution Metadata ---\n{json.dumps(metadata, indent=2)}" except Exception as e: await ctx.error(f"❌ Execution failed: {str(e)}") raise def log_tools(): """Log available tools and their descriptions.""" print("\nAvailable PowerShell Tools:") print("---------------------------") print("run_powershell:") print(" Description: Execute PowerShell commands securely") print(" Parameters:") print(" - code (string): PowerShell code to execute") print(" - timeout (int, optional): Command timeout in seconds (1-300, default 60)") print("\nget_system_info:") print(" Description: Get system information using Get-ComputerInfo") print(" Parameters:") print(" - properties (list, optional): List of properties to retrieve") print(" - timeout (int, optional): Command timeout in seconds (1-300, default 60)") print("\nget_running_services:") print(" Description: Get information about Windows services") print(" Parameters:") print(" - name (string, optional): Filter services by name (wildcards supported)") print(" - status (string, optional): Filter by status") print(" - timeout (int, optional): Command timeout in seconds (1-300, default 60)") print("\nget_processes:") print(" Description: Monitor running processes") print(" Parameters:") print(" - name (string, optional): Filter processes by name") print(" - top (int, optional): Limit to top N processes") print(" - sort_by (string, optional): Property to sort by") print(" - timeout (int, optional): Command timeout in seconds (1-300, default 60)") print("\nget_event_logs:") print(" Description: Access Windows event logs") print(" Parameters:") print(" - logname (string): Name of the event log to access") print(" - newest (int, optional): Number of recent events to retrieve") print(" - level (int, optional): Event level filter") print(" - timeout (int, optional): Command timeout in seconds (1-300, default 60)") print("---------------------------\n") # Resource definitions for exposing templates and system info @mcp.resource("template://{template_name}") def get_powershell_template(template_name: str) -> str: """Get a PowerShell script template by name.""" template_path = os.path.join(TEMPLATES_DIR, f"{template_name}.ps1") if not os.path.exists(template_path): raise ValueError(f"Template '{template_name}' not found") with open(template_path, 'r', encoding='utf-8') as f: return f.read() @mcp.resource("templates://list") def list_templates() -> str: """List all available PowerShell templates.""" if not os.path.exists(TEMPLATES_DIR): return json.dumps({"templates": [], "message": "Templates directory not found"}) templates = [] for file in os.listdir(TEMPLATES_DIR): if file.endswith('.ps1'): template_name = file[:-4] # Remove .ps1 extension template_path = os.path.join(TEMPLATES_DIR, file) try: with open(template_path, 'r', encoding='utf-8') as f: first_lines = f.readlines()[:10] # Read first 10 lines for description description = "" for line in first_lines: if line.strip().startswith('.SYNOPSIS'): # Find next non-comment line for description for desc_line in first_lines[first_lines.index(line)+1:]: if desc_line.strip() and not desc_line.strip().startswith('#') and not desc_line.strip().startswith('.'): description = desc_line.strip() break break templates.append({ "name": template_name, "description": description or f"PowerShell template: {template_name}", "path": f"template://{template_name}" }) except Exception as e: templates.append({ "name": template_name, "description": f"Template file (error reading: {str(e)})", "path": f"template://{template_name}" }) return json.dumps({"templates": templates}, indent=2) @mcp.resource("system://info") def get_system_info_resource() -> str: """Get basic system information as a resource.""" import platform try: import psutil system_info = { "platform": { "system": platform.system(), "release": platform.release(), "version": platform.version(), "machine": platform.machine(), "processor": platform.processor(), "node": platform.node() }, "memory": { "total_gb": round(psutil.virtual_memory().total / (1024**3), 2), "available_gb": round(psutil.virtual_memory().available / (1024**3), 2), "percent_used": psutil.virtual_memory().percent }, "disk": { "total_gb": round(psutil.disk_usage('/').total / (1024**3), 2) if os.name != 'nt' else round(psutil.disk_usage('C:\\').total / (1024**3), 2), "free_gb": round(psutil.disk_usage('/').free / (1024**3), 2) if os.name != 'nt' else round(psutil.disk_usage('C:\\').free / (1024**3), 2), }, "cpu": { "cores": psutil.cpu_count(), "percent": psutil.cpu_percent(interval=1) }, "timestamp": datetime.now().isoformat() } return json.dumps(system_info, indent=2) except ImportError: # Fallback if psutil is not available system_info = { "platform": { "system": platform.system(), "release": platform.release(), "version": platform.version(), "machine": platform.machine(), "processor": platform.processor(), "node": platform.node() }, "timestamp": datetime.now().isoformat(), "note": "Install psutil for detailed system metrics" } return json.dumps(system_info, indent=2) # Prompt definitions for PowerShell script generation guidance @mcp.prompt() def powershell_best_practices(script_purpose: str) -> str: """Generate a prompt for PowerShell script best practices.""" return f""" Please help me create a PowerShell script for: {script_purpose} Please ensure the script follows these best practices: 1. **Error Handling**: Include proper try-catch blocks and error handling 2. **Parameter Validation**: Use proper parameter validation and help text 3. **Logging**: Include informative Write-Host or Write-Output statements 4. **Security**: Avoid dangerous commands and validate inputs 5. **Documentation**: Include proper comment-based help with .SYNOPSIS, .DESCRIPTION, .PARAMETER, and .EXAMPLE 6. **Performance**: Use efficient PowerShell cmdlets and avoid unnecessary loops 7. **Compatibility**: Consider Windows PowerShell vs PowerShell Core compatibility Template structure: ```powershell <# .SYNOPSIS Brief description .DESCRIPTION Detailed description .PARAMETER ParameterName Description of parameter .EXAMPLE Example usage .NOTES Additional notes #> param( [Parameter(Mandatory=$true)] [string]$RequiredParam ) try {{ # Main script logic here Write-Host "Starting {script_purpose}..." # Your implementation Write-Host "Completed successfully" }} catch {{ Write-Error "Error occurred: $($_.Exception.Message)" exit 1 }} ``` Please provide a complete PowerShell script that implements the requested functionality while following these guidelines. """ @mcp.prompt() def troubleshoot_powershell_error(error_message: str, script_context: str = "") -> str: """Generate a prompt for troubleshooting PowerShell errors.""" context_section = f"\n\nScript context:\n{script_context}" if script_context else "" return f""" I'm encountering a PowerShell error and need help troubleshooting it. Error message: {error_message}{context_section} Please help me: 1. **Identify the root cause** of this error 2. **Provide specific solutions** to fix the issue 3. **Suggest preventive measures** to avoid similar errors in the future 4. **Recommend best practices** for the type of operation that's failing Please provide: - Clear explanation of what's causing the error - Step-by-step solution with corrected PowerShell code - Alternative approaches if applicable - Testing steps to verify the fix works If you need more information to provide a complete solution, please ask specific questions about: - PowerShell version being used - Operating system and version - Security context (admin privileges, execution policy) - Input data or parameters being used - Expected vs actual behavior """ if __name__ == "__main__": log_tools() mcp.run()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DynamicEndpoints/PowerShell-Exec-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server