Introducing PsExec for Python

Over the past few months I’ve been trying to find a way that gives people more options around running commands on a Windows host remotely. Currently you have a few options available to you that enable this;

  • Configure WinRM
  • Bake in commands to the startup process, like a Windows answer file or AWS user data
  • Use a tool like PsExec from another Windows host

The last point is where I am going to focus this blog post on, in particular I will talk about a new Python package called pypsexec.

Why PsExec

Before I go into the what, I need to explain why I am trying to run commands like PsExec and not just use something like WinRM. Ultimately PsExec has a few advantages over these protocols/tools like;

  • No custom service or agent is required on the host
  • It is only reliant on Server Message Block (SMB) which is setup and enabled on all Windows hosts
  • Due to the minimal dependencies, it is really simple to allow PsExec to connect remotely compared to WinRM
  • It is not platform dependent, can run on local hosts or hosts in AWS or some other cloud provider
  • Allows you to easily escape WinRM hell or run as the SYSTEM account (more info around WinRM limitation can be found on this blog post)

In saying that, the PsExec model does have a few disadvantages such as;

  • On Windows versions older than Server 2012 or Windows 8, there is no data encryption available
  • Can only authenticated with an account that is a member of the local Administrators group
  • May require some relaxing of Windows’s UAC settings if not in a domain environment
  • The overhead required to run a command means it is slow to start compared to WinRM

Ultimately I wanted to have an open source PsExec alternative that I can use in situations where WinRM is not available. This means I can

  • Use it to run bootstrapping scripts or adhoc commands on Windows host without requiring WinRM to be setup
  • Use this library to setup WinRM on newly installed hosts without the requirement of another Windows host
  • Reduce the time it takes to copy files, WinRM is really slow with file transfers while SMB is designed for this process
  • Satisfy my general curiosity around how PsExec works and get a better understanding of the SMB protocol

What is it

While PsExec is the most common name or term given to this process, it is actually a set of processes that is uses builtin protocols in Windows to work. The most common one is called PsExec and written by Mark Russinovich as part of the Sysinternals package. I’ll go into more details on how the protocol works further down but ultimately it leverages SMB and RPC to start a service on a Windows host and use that to execute the desired process.

While PsExec is probably the most popular tool that works with this model, there are a few other tools out there which offer similar capabilities. These tools are;

  • PAExec a free and open source replacement of PsExec and is used by pypsexec on the remote side
  • RemCom another free and open source project but hasn’t been updated since 2012 and has quite a few limitations
  • Impacket a Python implementation of multiple Windows protocols including the PsExec model but leverages RemCom on the remote side

There are some others out there but these are the only ones I know that work today. Unfortunately none of these tools really fit what I am looking for, the closest is Impacket but it has a few issues from my perspective. Ultimately I needed a way to run commands using the PsExec model that fit the following requirements;

  • Not reliant on Windows, this rules out PsExec, PAExec, and RemCom as they use Win32 APIs to talk to the remote Windows host
  • Works on the current supported versions of Windows, Impacket is ruled out as it uses RemCom which in turn doesn’t support 64-bit architectures
  • Can easily integrate into Ansible, Impacket is close but doesn’t support Python 3 and would make this step difficult

In the end I decided that I needed to write some code (turned out to be a lot) in Python to fit my requirements and ultimately that ended up with 2 Python libraries, smbprotocol and pypsexec.

Host requirements

One of the reasons I looked into using the PsExec model is because it didn’t require a lot of steps to setup on a Windows host. These are the only things that need to be done on the Windows host for this library to work;

  • Enable incoming traffic through port 445 – netsh advfirewall firewall set rule name="File and Printer Sharing (SMB-In)" dir=in new enable=Yes
  • The ADMIN$ share is enabled – this is enabled by default
  • Use a account that is a member of the local Administrators group
  • The connection user to have a full elevated (administrative) token on a remote logon

The first 3 requirements are quite simple to set up and can either be done using sysprep images or through things like an Windows answer file during the setup. The last requirement is probably the biggest stumbling block when it comes to using this tool. To understand this restriction you first need to understand logon tokens and how they work from Windows Vista and onwards. A logon token contains the rights and groups of the account during the initial logon and since Windows Vista, the token only contains the rights of a limited user account regardless if they are an administrator. You can still run processes with the full administrative rights that is granted to the user but it must go through UAC to elevate the token (Right click -> Run as Administrator).

When running things remotely, there is no GUI to right click and say Run as Administrator or any UAC prompt when the process asked for admin rights so it will fail. We need admin rights to be able to open SCMR and manage the service that will run our process. Ultimately for this to work we need the Windows host to not filter a remote logon token and this can be done through multiple ways;

  • In a domain environment, use any domain account that is a member of the local Administrators group
  • Any local Administrator will work if LocalAccountTokenFilterPolicy is set to 1 which disables the filtering
  • Use the builtin Administrator account (SID S-1-5-21-*-500), this account is typically disabled on desktop variants and this only works if AdminApprovalMode is not Enabled – this is not Enabled by default
  • For local accounts, any local Administrator will work if EnableLUA is not Enabled – this is Enabled by default
  • Disable UAC entirely (please don’t do this)

As you can see, a domain environment makes this simple as Windows will automatically use the full elevated token on a remote logon which satisfies our requirements. If using a local account, either the initial builtin Administrator account (without AdminApprovalMode) being enabled or another local admin account with LocalAccountTokenFilterPolicy being set to 1 will work. Disabling the EnableLUA option will also work but it also affects local processes and runs them under the full token by default, effectively bypassing UAC in those scenarios.

To disable the LocalAccountTokenFilterPolicy, you can run the following PowerShell script;

$reg_path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
$reg_prop_name = "LocalAccountTokenFilterPolicy"

$reg_key = Get-Item -Path $reg_path
$reg_prop = $reg_key.GetValue($reg_prop_name)
if ($null -ne $reg_prop) {
    Remove-ItemProperty -Path $reg_path -Name $reg_prop_name

New-ItemProperty -Path $reg_path -Name $reg_prop_name -Value 1 -PropertyType DWord

I will have to warn you, this can have some security implications for your Windows host so make sure you are aware of the risks and don’t follow the instructions blindly.

Using pypsexec

Now onto the fun part, getting it to run a command. The first step is to have a working Python install, you can run this on Python 2.6, 2.7, 3.4 and newer. To install the pypsexec library, simply run pip install pypsexec and it will be installed for you.

One simple command and it’s done

Once installed we need to create a simple Python script to call the library and tell it what commands to run. This is a very basic template you can use which runs the command whoami.exe /all under a specific account.

from pypsexec.client import Client

server = "server2016.domain.local"
username = "vagrant-domain@DOMAIN.LOCAL"
password = "VagrantPass1"
executable = "whoami.exe"
arguments = "/all"

c = Client(server, username=username, password=password,

    result = c.run_executable(executable, arguments=arguments)

print("STDOUT:\n%s" % result[0].decode('utf-8') if result[0] else "")
print("STDERR:\n%s" % result[1].decode('utf-8') if result[1] else "")
print("RC: %d" % result[2])

From there I can simply run the Python script with just python and here is the result from one of my servers

Output is just like it is locally

This is a basic example of running a process but pypsexec gives you control over multiple options like;

  • The number of processors it can run on
  • Whether to run the command asynchronously and not wait for a response, it will continue to run in the background
  • Whether to run it as an interactive process and what session to show that process on (the application will run on that session’s desktop)
  • Running as a different account than what was used in the connection process
  • Running as the local SYSTEM account to get godlike privileges on the process
  • Change the working directory
  • Set the priority of the process
  • Send bytes through the stdin pipe in case the remote process requires input
  • Set a timeout on the remote process

All these options and more can be found on the pypsexec Github page.

One extra feature that is not included by default is the ability to use Kerberos to authenticate and run a process as that user. This requires some Kerberos bindings to be installed on the host as well as the Python Kerberos packages. The system Kerberos bindings are dependent on the distro that is being used and once installed, needs to be configured. The Python packages can be installed by running pip install smbprotocol[kerberos]. This means that in the SMB authentication process, it will automatically attempt to authenticate with Kerberos if possible and continue on from there.

As I mentioned earlier, one of the reasons why I wanted to do this work was to add in a new way to run commands on a Windows host through Ansible. While, as of writing this blog post, it hasn’t been merged into the Ansible repository I have created a PR that you can start using today and try it out. This PR can be found here and any tests or feedback is greatly appreciated. To use this module in your own Ansible setup you will have to;

  • Install pypsexec and optional Kerberos dependencies as per usual on the host the module will run on
  • Copy down the file from that PR into a folder called library that is adjacent to your playbook or in a role directory

There are multiple examples in that PR on how you can use it but I will show you a simple example like the one above;

- name: run a simple command over the psexec module
  hosts: localhost
  gather_facts: no
  - name: run whoami /all on a Windows host
      hostname: server2016.domain.local
      connection_username: vagrant-domain@DOMAIN.LOCAL
      connection_password: VagrantPass1
      executable: whoami.exe
      arguments: /all
    register: whoami_output
    failed_when: whoami_output.rc not in [0, 1]

  - name: output stdout from psexec process
      var: whoami_output.stdout_lines

Here is what it looks like when run;

One of the benefits I spoke about of using this module is that you can provision a Windows host and use pypsexec to provision the WinRM listeners so Ansible can communicate with it normally. With this module you can easily do this by adding in the following task

- name: Download and run ConfigureRemotingForAnsible.ps1 to setup WinRM
    hostname: windows-pc
    connection_username: Administrator
    connection_password: Password01
    executable: powershell.exe
    arguments: '-'
    stdin: |
      $ErrorActionPreference = "Stop"
      $sec_protocols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault
      $sec_protocols = $sec_protocols -bor [Net.SecurityProtocolType]::Tls12
      [Net.ServicePointManager]::SecurityProtocol = $sec_protocols
      $url = ""
      Invoke-Expression ((New-Object Net.WebClient).DownloadString($url))

This will run a PowerShell that we pass in through the stdin option that downloads Ansible’s ConfigureRemotingForAnsible.ps1 and runs it on that Windows host. Once complete, your Ansible playbook can switch over to using the standard WinRM listener and continue as usual.

The next steps from here would be to look into turning this into a connection plugin within Ansible so that you aren’t limited to running commands but you can do things like copy and fetch files to the host as well as use all the Ansible PowerShell modules. This isn’t the be all to end all as the overhead required to run the process would make this quite slow and impractical to use over WinRM.

How it works

Now we know how to install and run this, it’s time to get down into some protocol details and how it all stacks together. Here is a basic process flow of how this all fits together.

Complex but gets the job done

As you can see, the majority of the network packets sent are done through SMB and in fact the RPC packets are encapsulated inside a specific SMB packet itself. The only part that is hard to describe in the process flow is the reading of the stdout and stderr pipes. What pypsexec does is runs those read requests as part of a separate thread while it is blocked waiting for the main PAExec pipe to return the process exit info. There can be multiple responses from the server during this process and these threads will continue to run until the remote process is finished. With the basic flow out of the way, let’s drill even deeper into each of the protocols that are used.


SMB standards for Server Message Block and depending on who you ask, is also known as CIFS or Samba. SMB is the actual protocol name while CIFS is an older dialect used by Microsoft historically. Samba is a suite of programs for Linux or Unix that is designed to inter operate with various Microsoft products like SMB or Active Directory. It is a protocol that is used for providing shared access to file, printer and pipes that operates on the OSI Application layer.

It can send data over numerous transports;

  • Directly over TCP with port 445
  • NetBIOS over TCP on ports 137 and 139
  • Other legacy protocols like NBF, IPC/SPX

We will only focus on the direct TCP transport over port 445 as that is what is most commonly used today and provided the largest packet sizes. Some key terms in used within the SMB protocol are;

  • Connection: Refers to the main connection to the server over TCP, this is the level in which the negotiate process occurs and there is typically one Connection per server
  • Session: Refers to an authenticated session of a Connection, there is typically one Session per user per server
  • Tree: Refers to a connected SMB share, like ADMIN$ and is run over a Session
  • Open: Refers to an open handle of a file, directory, printer, or pipe. This Open can govern what rights are allowed by a file operation based on the initial Open message and is run over a Tree
  • Dialect: The version of SMB that is supported and controls what features are available and in some limited scenarios, the format of a message


There have been numerous revisions and changes to the SMB protocol which ultimately results in a new dialect being created. The dialect controls what features are available to an SMB connection and can control what structure the messages ultimately takes. Here are some of the main SMB dialects that are still in use today

  • PC NETWORK PROGRAM 1.0, LANMAN1.0, Windows for Workgroups 3.1a, LM1.2X002, LANMAN2.1, NT LM 0.12: All SMBv1 dialects and are not used at all by smbprotocol
  • 2.0.0: Introduced with Server 2008 and Windows Vista
  • 2.1.0: Server 2008 R2 and Windows 7
  • 3.0.0: Server 2012 and Windows 8
  • 3.0.2: Server 2012 R2 and Windows 8.1
  • 3.1.1: Server 2016 and Windows 10

Starting with the 2.0.0 dialect, the structure of the SMB messages have remained consistent and is supported by all currently supported Windows versions. This means the benefits of supported the older SMB 1.0 dialects is quite minimal and can open a user up to more attack vectors which is something we want to avoid.

One of the biggest changes that affects end users of this project is the addition of message encryption in the 3.x dialects. This means that only Windows hosts based on Server 2012 or Windows 8 and newer support the encryption of messages sent to and from the clients. In today’s environment, this is definitely something we want to have and it is enabled by default on pypsexec.

Who knows what Microsoft will introduce in newer dialects but currently smbprotocol supports dialects 2.0.0 to 3.1.1 and most of the features in each dialect.


There are numerous types of messages in the SMBv2 protocol which I’ll briefly explain the major ones that are in use by pypsexec;

  • SMB2 Packet Header: The header of all requests and responses, it contains the metadata around the request and response
  • SMB2 NEGOTIATE: Used to negotiate the capabilities of the client and the server such as the dialect and encryption setup
  • SMB2 SESSION_SETUP: Used to authenticate a user and setup an SMB Session
  • SMB2 TREE_CONNECT: Used to connect to a Tree/Share on the remote host
  • SMB2 CREATE: Used to create an open handle to a file, directory, printer, or pipe
  • SMB2 READ: Used to read bytes from a file or pipe
  • SMB2 WRITE: Used to write bytes to a file or pipe
  • SMB2 IOCTL: Used to issue an implementation-specific FSCTL or IOCTL command across the network like an RPC bind
  • SMB2 TRANSFORM_HEADER: Used as the header for an encrypted message and can contain 1 or multiple SMB2 Packet Headers

The smbprotocol library exposes a function that can be used to create and send each of these messages based on a few input parameters. This makes it quite simple to send an SMB2 Read request by only passing in the file handle and the offset to read from. In most circumstances, a single packet is sent to the server but the SMB protocol allows compounded packets to be sent in one request.


One very important part of this process is to authenticate with a valid Windows account. This is most commonly done in SMB using the SPNEGO protocol to negotiate an authentication mechanism supports by both the client and the server. Currently smbprotocol can authenticate a local or domain account with either NTLM or Kerberos where Kerberos is the preferred option of the 2. The authentication process occurs straight after the negotiation response is received with the server’s SPNEGO token. This token contains a list of authentication mechanisms that are supported which smbprotocol compares against its own setup. If the Kerberos requirements are installed and setup up correctly and the remote host indicates it supports Kerberos in the SPNEGO token, smbprotocol will first attempt to authenticate with Kerberos before falling back to NTLM.

Once authenticated, both the NTLM and Kerberos protocols supply a unique session key which is then used by SMB to derive both the signing and encryption keys. The process to compute these keys are based on the dialect that was negotiated where newer dialects have a more complicated process for greater security. Once these keys are computed and an SMB Session is created, any future messages using that Session will be encrypted using that authenticated user context.


If the 3.0.0 or newer dialect is negotiated then SMB allows messages to be encrypted to ensure confidentiality of the data sent to and from the server. Unlike other Microsoft protocols which typically uses the GSSAPI/SSPI Wrap and Unwrap functions based on an authenticated context, SMB relies on it’s own process for encryption. Currently there are two different types of encryption that are supported in SMB;

  • AES 128-bit CCM
  • AES 128-bit GCM (Dialect 3.1.1 only)

In Dialect 3.1.1, the encryption cipher that is used is negotiated in the initial SMB2 NEGOTIATE message otherwise AES 128-bit CCM is used. Some servers require encryption on all shares and is set as a global setting otherwise it can be set as an individual share setting. This cannot be controlled by the client but rest assured, smbprotocol should support each scenario.

As an example, here is a Tree Connect message sent without encryption;

Here is the same message sent with encrypted;

In the message without encryption, I can easily see that I am connecting to the share \\server2016.domain.local\IPC$ whereas the encryption example I cannot even see what type of SMB message is being sent. While hiding what share I am connecting to can be important, encryption becomes even more useful when reading and writing on files and pipes so that a nefarious lurker can’t see the data.


RPC stands for Remote Procedure Call and is a way of running a procedure remotely but is coded like it would when running locally. Unfortunately the whole part of calling a procedure remotely like it would be done locally is lost when it comes to this process. This is because the usual RPC layer that handles this abstraction does not support the Windows specific functions. This means that the pypsexec library needs to implement that RPC layer when calling the functions that are required. For pypsexec, we use RPC to interact with the Windows Service Control Manager Remote (SCMR) API so that we can manage the Windows service that runs our remote payload. The RPC process is as follows;

  • A new SMB Open is created on the IPC$ tree for the pipe svcctl
  • An SMB Write packet is sent to the opened pipe that contains the DCE/RPC Bind PDU structure
  • The Bind Acknowledgement response is parsed to ensure the Bind didn’t fail
  • Any SCMR calls will then send an SMB IOCTL request that contains the method and parameters to invoke on the remote host
  • Once all the SCMR tasks are complete, the SMB Open is closed which also closes the binding

This was a complicated protocol to understand and I only really just scratched the surface to get the Python library working with SCMR. I’m sure there are a lot of major details I am missing or misunderstood but so far it is working and I don’t have a full need to move past it.


SCMR stands for Service Control Manager Remote and is a protocol that is used to remotely manage Windows services. It can do things remotely like;

  • Enumerate services
  • Start/stop services
  • Create/delete services
  • Modify services
  • Lots and lots more, see the MS-SCMR docs for a full list of functions available

It is run over RPC which in turn is run over SMB pipes and on a typical Windows setup, this is all abstracted with the local RPC implementation on that host. This implementation would marshal the data that is used in the function into a byte structure and send that through the network as well as parse and marshal the responses back to the client. As mentioned in the RPC section, this is unavailable on a non-Windows host and so we have to do all this work ourselves. The current pypsexec code only has to deal with 2 different types of variables, integers and strings. Integers are packed like normally in Python as a little-endian byte but strings are a bit more complex. String have a structure like

RCP string
    Int32 ReferenceID - A unique ID to set for the string, the uniqueness is not really implemented in pypsexec and we just set it as 1 if required otherwise it is a 0 byte value
    Int32 MaxCount - The numbers of chars in the Bytes field when returned by the server, this is just set to ActualCount
    Int32 Offset - The offset of the Bytes value
    Int32 ActualCount - The number of chars (not Bytes) of the Bytes field when encoded including the NULL terminator
    Bytes Bytes - The string that is encoded as a byte string, typically this is UTF-16-LE encoded with a null terminator
    Bytes Padding - The Bytes field must align to a 4-byte boundary so this is just some NULL bytes to pad the length

Now that the basic data marshaling is covered, pypsexec must add support for invoking the required functions in SCMR. This is done by creating a Request PDU as defined in the RCP/DCE 1.1 documentation and send that over as a FSCTL_PIPE_TRANSCEIVE SMB IOCTL Request. Each function has a particular operation number (opnum) that is set on the Request PDU and the data is just the marshaled bytes of the function’s input parameters. The response contains at least the return code that identifies the result of the invocation and can also contain other return values based on the function that was called.

As an example, let’s dive into the ROpenServiceW function and show what happens with the data being passed to and from the client. The function is defined in MS-SCMR as;

DWORD ROpenServiceW(
  [in] SC_RPC_HANDLE hSCManager,
  [in, string, range(0, SC_MAX_NAME_LENGTH)] 
    wchar_t* lpServiceName,
  [in] DWORD dwDesiredAccess,
  [out] LPSC_RPC_HANDLE lpServiceHandle

This means it takes in 3 input parameters hSCManager, lpServiceName, dwDesiredAccess and return 2 values; lpServiceHandle and a DWORD/Int32 that indicates the function result. If we wanted to open a handle to the service called Test Service with the rights to query, start, and stop a service here is what it would look like;

The hSCManager was created as a unique handle as part of a previous call to SCMR, in this example we will just pretend it is 20 bytes of 0xFF. The string Test Service does not need a unique/referent ID and the marshaled string structure would look like

MaxCount: 0D 00 00 00
Offset: 00 00 00 00
ActualCount: 0D 00 00 00
Bytes: 54 00 65 00 73 00 74 00 20 00 53 00 65 00 72 00 76 00 69 00 63 00 65 00 00 00
Padding: 00 00

The MaxCount and ActualCount is equal to 0x0D which is 13 in decimal form while the string is encoded as a UTF-16-LE string with a null terminator. Because the UTF-16-LE encoded string is 26 bytes long, we need to pad it with 2 null bytes so it is aligned to the 4-byte boundary.

The dwDesiredAccess requires 3 flags that are set to get the query, start, and stop rights which are;

  • SERVICE_QUERY_STATUS: 0x00000004
  • SERVICE_START: 0x00000010
  • SERVICE_STOP: 0x00000020

When combined this results in an integer of 52 and the packed bytes value for this is 34 00 00 00. Putting this all together, the byte structure that is sent with the RPC Request PDU is;

0D 00 00 00
00 00 00 00
0D 00 00 00
54 00 65 00
73 00 74 00
20 00 53 00
65 00 72 00
76 00 69 00
63 00 65 00
00 00 00 00
34 00 00 00

When sent with the RPC Request PDU, the opnum is set to 16 and that packed as a Int16 is 10 00. The server would then receive the request, unpack the data and execute the function and finally return the result under an RPC Response PDU. This response would look like;

00 00 00 00

The first 20 bytes is the handle for the service Test Service, in this case is 20 bytes of 0xAA while the return code is 0 which means it was successful.


While all this is done on the client side, we still need some executable to run as the Windows service and execute the requested process. Unfortunately while PsExec is free it is not licensed for distribution which means I can’t legally use the PsExec executable to run as the service payload. In comes PAExec which is an open source alternative and can be distributed in other projects without a fee. PAExec is designed to be a drop in replacement to PsExec and offers the same features as well as some others that PsExec doesn’t supply and for this project I am just using the service component. If you are interested in delving into the code for PAExec you can find it on its own Github page.

The process around PAExec is;

  • Copy the PAExec payload to C:\Windows\PAExec-<localhostname>-<localpid>.exe
  • Create a service that calls C:\Windows\PAExec-<localhostname>-<localpid>.exe -service to start the PAExec service
  • This will create a Named Pipe on the remote host called PAExec-<localhostname>-<localpid>.exe
  • Send the PAExec process settings (contains info such as the executable to run) to that Named Pipe
  • Send the PAExec start message to the main Named Pipe
  • PAExec will start the process and create three more Named Pipes, PaExecOut<localhostname><localpid>, PaExecErr<localhostname><localpid>, PaExecIn<localhostname><localpid> which is the stdout, stderr and stdin for the remote process
  • The client will create a separate thread for both the stdout and stderr and continually read from that pipe
  • The client will also create a read request for the main Named Pipe to get the process output
  • This read on the main Named Pipe will wait until the remote process finishes, during this time the stdout and stderr thread will have stored each read response in a buffer
  • Now the process has ended, the client has the stdout and stderr bytes as well as the return code from the remote process.

This can be repeated multiple times and once everything is run, the client can then cleanup the payload and service that remains on the Windows host. One of the biggest challenges I faced when creating this process was how to read from the stdout and stderr pipe as the main named pipe will not complete until those buffers are read and empty. To ensure we don’t block any process in case there is no more data to be read from the pipes. What happens now is that the stdout and stderr pipes will keep on polling the remote pipe until it has been closed (the process is finished) as a separate thread. There is bound to be some more optimisations that can occur in this space but for a 1.0 product it works for me.

The biggest downfall of PAExec is that the initial settings end over the main Named Pipe is not encrypted but just encoded using a simple XOR pass. This means for Windows hosts that cannot use SMB encryption, any of these details can be viewed by anyone.

For the future

Currently the pypsexec and smbprotocol gives you the ability to run a process on a Windows host but it is not perfect. In the future I am looking to add in the following features;

  • I still occasionally see some deadlocks when running a command, this needs to be solved but it is quite hard to debug and reproduce on demand
  • An interactive shell for pypsexec that takes input from stdin and outputs the responses to stdout and stderr
  • Simple script bundled with pypsexec to take in arguments and run the command like the PsExec.exe executable
  • Add a higher level API to smbprotocol that does things like create/deleting files or reading/writing data to a file using simple functions
  • Find ways to improve the speed of smbprotocol
  • Move beyond a simple Ansible module and turn it into a connection plugin so it can be used to run all the existing Windows modules instead of just commands

Some of this stuff is easy to do but others are quite complex and depend on other things to be in place to be considered viable. If you feel up to the challenge or just come across a bug, feel free to submit an issue or a pull request on Github.

Notify of

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Inline Feedbacks
View all comments
Stanislav Dmytrenko
2 years ago

Hi, Jordan.
I’m trying to use this library for my test automation.
Could you please point me, how to run a batch file on remote computer?

2 years ago


Running a batch file should be as simple as specifying the path to that file as the executable argument like so

from pypsexec.client import Client

c = Client("hostname", username="username", password="password")


stdout, stderr, rc = c.run_executable(r"C:\path\to\batch.bat")

There are multiple arguments you can specify to control how the process is run, more info on that can be found at the README of the pypsexec project.



1 year ago

Jordan, I am trying to use pypsexec and for what ever reason it is not actually running the the commands I pass to it to run on the remote system. Below is my code snip it, any ideas why it does not actually run them.

if answer in tz:
connection(r"tzutil /s " + tz[answer])
print("What time zone was that?")

def main():
install = “c:\nocprinttool\install.bat”
shutil.rmtree(“\\” + computer + “\c$\nocprinttool”)

def connection(app):
user = os.getenv(‘UserName’)
c = Client(computer, username=user, password=passwd)

1 year ago
Reply to  Jordan Borean

Jordan, I got it to run the batch command but when trying to run a system command such as tzutil.exe, it returns unable to file the file even though I have specified the full path to the file. which is c:\Windows\System32\? Anythoughts why it is not running the command. I set python to print stdout but it fails before it prints the stdout. It does return the following in the traceback:
pypsexec.exceptions.PAExecException: Received exception from remote PAExec service: Failed to start “c:\Windows\System32\tzutil.exe /s “Mountain Standard Time””. The system cannot find the file specified. [Err=0x2, 2]

Sagar Kolli
1 year ago


I am trying to execute command on my remote machine with run_executable. Executable and arguments are too long for the run_excutable to handle. How can i increase the buffer size?

Below is the error

c.run_executable(executable4, arguments=arguments4)
File “/usr/local/lib/python3.6/dist-packages/pypsexec/”, line 413, in run_executable
File “/usr/local/lib/python3.6/dist-packages/pypsexec/”, line 108, in check_resp
raise PAExecException(msg_id, self[‘buffer’].get_value())
pypsexec.exceptions.PAExecException: Received exception from remote PAExec service: Failed to start “MOVE /Y” C:\Tomcat8048_atlasprd\apache-tomcat-8.0.48\bin\tomcat8.exe....\logs* C:\Tomcat8048_atlasprd\apache-tomcat-8.0.48\bin\tomcat8.exe....\logs\oldlogs. The system cannot find the file specified. [Err=0x2, 2]

Sagar Kolli
1 year ago
Reply to  Jordan Borean

Thank You so much Jordan it worked. One more issue i am receiving timed out errors from PAExec Pipe. How ever script is getting executed as required. How can avoid these errors? //usr/local/lib/python3.6/dist-packages/pypsexec/ TheadCloseTimeoutWarning: Timeout while waiting for pipe thread to close: PaExecOutvinfraubuntu22204 %, TheadCloseTimeoutWarning) Exception in thread PaExecOutvinfraubuntu22204: Traceback (most recent call last): File “/usr/local/lib/python3.6/dist-packages/pypsexec/”, line 182, in run pipe_out = read_resp_func(request) File “/usr/local/lib/python3.6/dist-packages/smbprotocol/”, line 1107, in _read_response response = self.connection.receive(request, wait=wait) File “/usr/local/lib/python3.6/dist-packages/smbprotocol/”, line 1021, in receive self._flush_message_buffer() File “/usr/local/lib/python3.6/dist-packages/smbprotocol/”, line 1128, in _flush_message_buffer message_bytes = self.transport.receive() File “/usr/local/lib/python3.6/dist-packages/smbprotocol/”, line 99, in receive packet_size_bytes = self._recv(4) File… Read more »

Sagar Kolli
1 year ago
Reply to  Jordan Borean

How can i use timeout on an executable?

Kasper Nielsen
Kasper Nielsen
1 year ago

Hello Jordan! First, awsome blog you have, really interesting reads 🙂 However im having some problems with using psexec with python, im hoping you could point me to the issue. Everyhting works fine when i run the commands manually so its not a network/permission or such issue, its something im missing with my programming. Anyway the problem, the commands im trying to run just arent being run when using python to do it for some reason? Here is a screenshot of me manually starting the calculator remotely, and the pyhthon code im trying to use to automate the same command,… Read more »

Sagar Kolli
1 year ago

Hi Jordan, How do i catch below exceptions from run_executable. My script is running without any issues but i am not able to catch these exceptions. /usr/local/lib/python3.6/dist-packages/pypsexec/ TheadCloseTimeoutWarning: Timeout while waiting for pipe thread to close: PaExecErrvinfraubuntu13480 %, TheadCloseTimeoutWarning) Exception in thread PaExecErrvinfraubuntu13480: Traceback (most recent call last): File “/usr/local/lib/python3.6/dist-packages/pypsexec/”, line 182, in run pipe_out = read_resp_func(request) File “/usr/local/lib/python3.6/dist-packages/smbprotocol/”, line 1107, in _read_response response = self.connection.receive(request, wait=wait) File “/usr/local/lib/python3.6/dist-packages/smbprotocol/”, line 1021, in receive self._flush_message_buffer() File “/usr/local/lib/python3.6/dist-packages/smbprotocol/”, line 1128, in _flush_message_buffer message_bytes = self.transport.receive() File “/usr/local/lib/python3.6/dist-packages/smbprotocol/”, line 99, in receive packet_size_bytes = self._recv(4) File “/usr/local/lib/python3.6/dist-packages/smbprotocol/”, line 123, in _recv raise… Read more »

1 year ago

I’m use ip addres. Python script it work. But ansible playbook error: The full traceback is: File “/tmp/ansible_psexec_payload_5kj015oj/”, line 457, in main win_client.connect(timeout=connection_timeout) File “/usr/lib/python3.7/site-packages/pypsexec/”, line 60, in connect self.connection.connect(timeout=timeout) File “/usr/lib/python3.7/site-packages/smbprotocol/”, line 835, in connect self.transport.connect() File “/usr/lib/python3.7/site-packages/smbprotocol/”, line 60, in connect self._sock.connect((self.server, self.port)) fatal: [localhost]: FAILED! => { “changed”: false, “invocation”: { “module_args”: { “arguments”: “/all”, “asynchronous”: false, “connection_password”: “VALUE_SPECIFIED_IN_NO_LOG_PARAMETER”, “connection_timeout”: 60, “connection_username”: “VALUE_SPECIFIED_IN_NO_LOG_PARAMETER”, “encrypt”: true, “executable”: “whoami.exe”, “hostname”: “”, “integrity_level”: “default”, “interactive”: false, “interactive_session”: 0, “load_profile”: true, “port”: 445, “priority”: “normal”, “process_password”: null, “process_timeout”: 0, “process_username”: null, “show_ui_on_logon_screen”: false, “stdin”: null, “working_directory”: “C:\Windows\System32” } }, “msg”: “[Errno… Read more »

1 year ago

Hi Jordan, Nice work, congrats. I am trying to invoke a script written in Python and converted to exe (using pyinstaller); say to file1.exe; the file imports few other py files. This file1.exe is copied over to Machine-Local, which do not have python environment. If the file is invoked from the Machine-Local, it works. But if the same file is invoked from machine-remote, file1.exe starts executing, but fails as it couldn’t find the py files that are part of imports. This is what I am using: from pypsexec.client import Client server = “” username = “LOCAL\admin” password = “12345”… Read more »

marius danciu
marius danciu
1 year ago

I need to run a python remotely but not running without puting it in a batch file
If I do this:
stdout, stderr, rc = c.run_executable(“c:\releaseman\UBEV\”, working_dir=”c:\releaseman\UBEV”) I get the following error:
pypsexec.exceptions.PAExecException: Received exception from remote PAExec service: Failed to start “c:\releaseman\UBEV\”. Unknown error value. [Err=0xC1, 193]
Note: I’ve tried to give working_dir = c:\ptyhon32 (same error)

But if I create a batch file (bat containing (python
stdout, stderr, rc = c.run_executable(“c:\releaseman\UBEV\test_lcms.bat”, working_dir=”c:\releaseman\UBEV”) — this works, no error

Could you give me a hint of how to execute a python script remotely?

marius danciu
marius danciu
1 year ago
Reply to  Jordan Borean

thanks a lot!, it worked using your first proposed approach… In the end I’ve managed to obtain what I needed :
stdout, stderr, rc = c.run_executable(“c:\Python32\python.exe”, arguments=’c:\releaseman\UBEV\ -c “Unpublished Components” -b 358 -s solution -i′, username=”admin”, password=”pass”)

basically the executed script should connect back to the initiator machine and copy some folders… it worked only after provided explicitly username and password

Prasanna Kolmbkar
1 year ago

Hi Jordan

I am using pypsexec to run some python-selenium scripts on remote windows server but it seems that the screen resolution in this rpc is very small as compared my actual screen. and therefore my selenium scripts are failing can you help me with some soloution or alternatives

By the way Amazing Post, Keep up the good Work.

1 year ago

I have couple of queries:
1. How to disable Debig log message on console:
07-May-2019 05:33:39 PM – smbprotocol.tree – 269 – None – INFO – Session: Administrator, Tree
07-May-2019 05:33:39 PM – smbprotocol.tree – 273 – None – DEBUG – SMB2TreeDisconnect:

How to execute powershell commands using this module ?

1 year ago

Thanks Jordan for the reply.
An another issue I am facing is while executing command (on remote host) which takes input from local file, the file is getting appended with characters ‘\r\n’ while runtime and resulting in authentication error.
e.g. cmd.exe /c “command -login -input auth.txt”

Any means ti avoid file modification or any way to send password on Windows shell prompt itself ?

9 months ago


Im trying pypsexec to run a .cmd batch file on remote server as per your example in the comment section.

I get STDERR: ‘dfsutil.exe’ is not a recognized as an internal or external command. the cmd file is basic, has command “dfsutil.exe link add \linkpath \targetpath.

I checked the remote server and the dfsutil command works as expected with the account im using in pypsexec. Environement vars PATH are present. Also tried specifying full path to dfsutil but no luck.


robert kabwogi
robert kabwogi
8 months ago

Hi Jordan,
I am trying to use your package to do some backup automation like login to the server remotely create backup file and copy created file from that connected server to the other remote server directly but when i try to copy i get access denied and when i map network drive i get drive not found.
Do you have any advise on how to better do this if possible?

8 months ago

i want to run cpqsetup.exe (which is a python executable which prints hello) from the remote server . Executing below command,
stdout, stderr, rc = c.run_executable(“C:\test\cpqsetup.exe”, use_system_account=True)
The executable in invoking in remote machine but it get stucks. Output is not displaying and the command hangs. Could you please help how to execute the cpqsetup.exe from the remote system.

8 months ago

Hi Jordan, I am using your module as part of salt-cloud to install salt-minion. But i get the following error: Traceback (most recent call last): File “/usr/lib/python3.6/site-packages/salt/utils/”, line 1015, in wait_for_psexecsvc ‘cmd.exe’, ‘/c hostname’, host, username, password, port=port File “/usr/lib/python3.6/site-packages/salt/utils/”, line 962, in run_psexec_command client = Client(host, username, password, port=port, encrypt=False, service_name=service_name) File “/usr/lib/python3.6/site-packages/salt/utils/”, line 875, in init self._client = PsExecClient(server, username, password, port, encrypt) NameError: name ‘PsExecClient’ is not defined [DEBUG ] Retrying psexec connection to host on port 445 (try 14) [ERROR ] Unable to execute command I have all the dependencies installed. It was working… Read more »

7 months ago

Hi Jordan,
I am facing below issue, could you please suggest.
File “C:\Users\Administrator\Desktop\test-inj\util\”, line 234, in runtemp
File “C:\Users\Administrator\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pypsexec\”, line 112, in remove_service
File “C:\Users\Administrator\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pypsexec\”, line 326, in delete
File “C:\Users\Administrator\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pypsexec\”, line 478, in delete_service
self._parse_error(return_code, “RDeleteService”)
File “C:\Users\Administrator\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pypsexec\”, line 695, in _parse_error
raise SCMRException(function_name, return_code, error_string)
pypsexec.exceptions.SCMRException: Exception calling RDeleteService. Code: 1072, Msg: ERROR_SERVICE_MARKED_FOR_DELETE

7 months ago

Hello Jordan,
Does Pypsexec support run custom exe file on remote computer?

6 months ago

Hi Jordan, I’m trying to run exe on 32bit and 64bit remote PC, but there is a difference in impersonation on 32bit machine. For executing command as local user on remote machine I previously used Task Scheduler, but that solution is so complicated and not reliable (register custom task, trigger it, then delete it…). I tried with this library and it works for 64bit windows 10, but on 32bit windows 10 exe is running as System Account and not as local user that I specify. Did you maybe know what is the problem and how to solve it? Thanks in… Read more »

5 months ago


When I connected windows2012, I got the following error. What is the specific reason?
smbprotocol.exceptions.SMBResponseException: received unexpected status from the server: (3221225649) STATUS_PIPE_CLOSING: 0xc00000b1

5 months ago

Hi Jordan,

Yes, this problem occurs on some servers. In addition, I also checked whether those servers have psexec connection conditions, and I found that they are all. Excuse me, is there any solution?

4 months ago

Hi Jordan, Thanks for the detailed explanation about the pypsexec library the functionalities. I’m sorry if I am repeating this question. I’m trying to connect AWS Windows EC2 machine from AWS EC2 Linux machine. I’m using Python 2.7 and my code is similar to your example code with the appropriate hostnames and credential replacement. I could see a process “PAExec-4614-ip-xx-0-1-xxx.ec2.internal.exe” is running and a related file under Windows $ADMIN directory and also in the same name of the process a service has been created at the Windows server’s end. But unfortunately Python is hung at Linux server’s end and running… Read more »

3 months ago

Hi Jordan,

I’m trying to access files from windows remote server by using python script. Can you please help me how to write python code for this.

3 months ago

Hi Jordan,

As per your suggestion
I wrote the code like

with smbclient.open_file(r”\server_name\c:\software\file.txt”, username=’username’, password=’password’) as fd:
file_contents =

but it gives below error
smbprotocol.exceptions.SMBResponseException: Received unexpected status from the server: (3221225676) STATUS_BAD_NETWORK_NAME: 0xc00000cc

3 months ago

Hi jordan,

Thank You. It’s working.

One more doubt.

Is it possible to check the remote path type i.e, folder, file or zipfile by using smbclient ?

Actually my intention is…..
— read the content when remote windows path is file type.
— count the number of files when remote windows path is a directory.
— unzip the file and count number of files when remote windows path is a directory.

Is smbclient is supported for my requirements or not ?
Can you please give me the explanation jordan. It’ll be helpful to me a lot.

3 months ago

Hi jordan, That was useful to me. But I want to work on Zip files also without copying into local. For that I’ll go for pypsexec. I wrote the code like below. But connection was not created and raise exception like. Code is: from pypsexec.client import Client ip = ‘server’ username = ‘username’ password = ‘password’ executable = ‘powershell.exe’ c = Client(server=ip, username=username, password=password) c.connect() try: c.create_service() stdout, stderr, rc = c.run_executable(executable, interactive=True, interactive_session=2, use_system_account=True,) print(stdout) finally: c.remove_service() c.disconnect() Error is: Traceback (most recent call last): File “C:/Workspace/RaviteJa/Tasks/”, line 15, in use_system_account=True,) File “C:\Users\raviteja_maram\AppData\Roaming\Python\Python37\site-packages\pypsexec\”, line 412, in run_executable exe_result.check_resp() File… Read more »

Nick Kharchenko
Nick Kharchenko
2 months ago

Hey Jordan,

I’m trying to run a powershell script on a remote host. I believe I need to use the stdin parameter.

The request looks like this:

executable = “powershell.exe”
arguments = “-”
input = |
Write-Host Hello World
Write-Error Error Message
exit 0

c = Client(server, username=username, password=password,

result = c.run_executable(executable,arguments=arguments,stdin=input)

I’m struggling on parsing through the request as I’m getting the following error:

input = |
SyntaxError: invalid syntax

Any suggestions/tips would be super helpful!

Pranjal Mahajan
Pranjal Mahajan
2 months ago

I am having the below error, can you help?

Traceback (most recent call last):

File “”, line 1, in
runfile(‘C:/Users/pmahajan/.spyder-py3/’, wdir=’C:/Users/pmahajan/.spyder-py3′)

File “C:\Users\pmahajan\AppData\Local\Continuum\anaconda3\lib\site-packages\spyder_kernels\customize\”, line 827, in runfile
execfile(filename, namespace)

File “C:\Users\pmahajan\AppData\Local\Continuum\anaconda3\lib\site-packages\spyder_kernels\customize\”, line 110, in execfile
exec(compile(, filename, ‘exec’), namespace)

File “C:/Users/pmahajan/.spyder-py3/”, line 37, in
result = checking_NAS_connection(c,executable1,arguments1)

File “C:/Users/pmahajan/.spyder-py3/”, line 21, in checking_NAS_connection

File “C:\Users\pmahajan\AppData\Local\Continuum\anaconda3\lib\site-packages\pypsexec\”, line 112, in remove_service

File “C:\Users\pmahajan\AppData\Local\Continuum\anaconda3\lib\site-packages\pypsexec\”, line 326, in delete

File “C:\Users\pmahajan\AppData\Local\Continuum\anaconda3\lib\site-packages\pypsexec\”, line 478, in delete_service
self._parse_error(return_code, “RDeleteService”)

File “C:\Users\pmahajan\AppData\Local\Continuum\anaconda3\lib\site-packages\pypsexec\”, line 695, in _parse_error
raise SCMRException(function_name, return_code, error_string)

SCMRException: Exception calling RDeleteService. Code: 1072, Msg: ERROR_SERVICE_MARKED_FOR_DELETE

2 months ago

Hi Jordan ,
Really nice blogs. I have a query here
I have a GUI application like Slack and I am using pypsexec libraby to lauch it remotely from linux m/c. I am able to see that process launch on Windows but unfortunately its GUI is not launching on windows. Would you please help me to acheive it

Pallavi E
Pallavi E
20 days ago

Hi Jordan, Thanks for helping us run commands on remote windows host. It has been a struggle for so many years. I am facing issue with my script. Here is my script : from pypsexec.client import Client c = Client(“server”, username=”uname”, password=”password”) c.connect() try: c.create_service() time.sleep(10) print(“Finsihed Hello world”) c.run_executable(“powershell.exe”, arguments=”/c Get-InitiatorPort | Select-Object -Property PortAddress”) finally: c.cleanup() c.disconnect() Error : smbprotocol.exceptions.SMBResponseException: Received unexpected status from the server: (3221225649) STATUS_PIPE_CLOSING: 0xc00000b1 When i run the above script i am getting the error mentioned. But , When i execute same line by line in python console. for first time execution of… Read more »

Don Ressler
13 days ago

Hi Jordan,

I am trying to run the below script and to target application it sends only argument ‘Daily’ and not complete string ‘Daily File load’. can you please help?

from pypsexec.client import Client
, arguments=’Daily File Load’)