Execute PowerShell from Python

I originally wrote this to execute PowerShell scripts from a Flask web interface. You can now install Powershell on Linux. Crazy, ain’t it?

Python library code. I named it lib_powershell.py

import subprocess
import datetime
import time
import os
import ast
import json

# ======================================================= 
#                                               ~@.*.@~
# 66½                                              y 
# =======================================================

'''
Windows service running as Flask to execute
PowerShell from web call.
'''
class PowerShellProc():
    """
    WEB REQUEST <- JSON {requestor: "auth_user_id", return_code: 200, request_package: {}, timestamp: nnn}
    OUT -> JSON (return code, {key: val})
    """

    def __init__(self):
        self.pwrshell_path = r'C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe'
        self.station = r'\\mswg9\groups\integration\repo\ps'
        self.marshal = 'PSCop.ps1' # Auth: MSA and exec actual PS scripts based on request.

    def exec_pwrshell(self, pwr_script=None, script_path=None, args=[], ps_tuple_to_py=True, gm_flag=False, date2str=False):
        '''Out.exec_pwrshell('psscript.ps1' ,script_path=None, ps_tuple_to_py=True)
        script_path: overrides envrionment path.exists
        ps_tuple_to_py: Powershell tuples convert to dictionaries, set True to return a Python tuple.
        '''
        data = None
        exec_path = self.pwrshell_path
        
        if not os.path.exists(exec_path):
            raise IOError('Powershell not found at %s.' % exec_path)

        if not script_path:
            script_path = self.station

        if not os.path.exists(script_path):
            raise IOError('Script path not found at %s.' % script_path)

        if pwr_script:
            script_path = os.path.join(script_path, pwr_script)
        else:
            script_path = os.path.join(script_path, self.marshal)

        cmd = [exec_path, script_path]

        if args:
            if not isinstance(args, list):
                raise TypeError("Arguments must be passed as a list not %s." % str(type(args)))
            cmd.extend(args)

        proc = subprocess.Popen(cmd,
                    shell=True,
                    stdin=subprocess.PIPE,
                    stdout=subprocess.PIPE,
                    )

        rtrn,perr = proc.communicate()
        rcode = proc.returncode

        #if rtrn:
            # Line directly below converts PowerShell Json to Python data structs.
            #data = self.ps_output_converstion(rtrn, tup=ps_tuple_to_py, gm=gm_flag, date2str=date2str)

        if perr:
            # Error string...
            data = json.dumps(perr.decode('utf-8','ignore'))
        else:
            #data = rtrn.decode('utf-8','ignore')
            data = self.ps_output_converstion(rtrn, tup=ps_tuple_to_py, gm=gm_flag, date2str=date2str)

        return rcode,data

    def ps_output_converstion(self, res, tup=True, gm=False, date2str=True):
        '''Converts Powershell string output to Python type'''

        res = res.decode('utf-8','ignore')

        if '\n' in res:
            res = res.replace('\n','')

        if '\r' in res:
            res = res.split('\r')[0]

        res = ast.literal_eval(res)

        # Data conversion if necessary.
        if type(res) == str:
            if 'date' in res.lower() or 'time' in res.lower():
                epoch_elem = elem[elem.find("(")+1:elem.rfind(")")]
                dt = self.ps_epoch_to_datetime(float(elem), gm=gm, date2str=date2str)
                res = dt

        if type(res) == list:
            for index, elem in enumerate(res):
                if 'date' in str(elem).lower() or 'date' in str(elem).lower():
                    epoch_elem = elem[elem.find("(")+1:elem.rfind(")")]
                    dt = self.ps_epoch_to_datetime(float(elem), gm=gm, date2str=date2str)
                    res.remove(elem)
                    res.insert(index,dt)

        if type(res) == dict:
            for field, value in res.items():
                if 'date' in str(value).lower():
                    epoch_elem = value[value.find("(")+1:value.rfind(")")]
                    dt = self.ps_epoch_to_datetime(float(epoch_elem), gm=gm, date2str=date2str)
                    res[field] = dt

            if tup:
                if 'Item1' in res:
                    l = [v for v in res.values()]
                    res = tuple(l)

        return res

    def ps_epoch_to_datetime(self, epoch_time, gm=False, date2str=False):
        '''Converts Powershell Date to Python datetime.'''
        
        if gm:
            struct = time.gmtime(epoch_time/1000.)
        else:
            struct = time.localtime(epoch_time/1000.)

        dt = datetime.datetime.fromtimestamp(time.mktime(struct))

        if date2str: dt = dt.ctime()
        return dt

    def py2json(self, data):
        return json.dumps(data) # This will convert datetime to string.

    def json2py(self, data):
        return json.loads(data)

Here’s the PowerShell that returns .Net data, uncomment what you want to test. I named it test.ps1

$Data = "Hello"
#$Data = 1
#$Data = 2.5
#$Data = [DateTime]"January 1, 2018 6:00 AM"
#$Data = [System.Tuple]::Create("Theresa Wilson", [DateTime]"January 1, 2018 6:00 AM", 1)
#$Data = @( "a","b","c","d","e",1,2,3 )
#$Data = [System.Collections.ArrayList]$listArray = @( "a","b","c","d","e",1,2,3)
$Data = @{"Washington" = "Olympia"; "Oregon" = "Salem"; California = "Sacramento"}

#$Data = $null # Do not return NULL, Python doesn't understand it, return zero instead.

$Data | ConvertTo-Json -Compress

Write-Host $Data

#Write-Output $Data

Test Python Script.

import lib_powershell

ps = lib_powershell.PowerShellProc()

ps.exec_pwrshell(pwr_script='test.ps1',script_path=r'C:\Path',ps_tuple_to_py=False) # Tuple to dict.
(0, {'Item3': 1, 'Length': 3, 'Item1': 'Theresa Wilson', 'Item2': datetime.datetime(2018, 1, 1, 6, 0)})
 
ps.exec_pwrshell(pwr_script='test.ps1',script_path=r'C:\Path')
(0, (1, 3, 'Theresa Wilson', datetime.datetime(2018, 1, 1, 6, 0)))
 
ps.exec_pwrshell(pwr_script='test.ps1',script_path=r'C:\Path', gm_flag=True) # Mixed Tuple
(0, (1, 3, 'Theresa Wilson', datetime.datetime(2018, 1, 1, 11, 0)))
 
ps.exec_pwrshell(pwr_script='test.ps1',script_path=r'C:\Path')
(0, {'Oregon': 'Salem', 'Washington': 'Olympia', 'California': 'Sacramento'})
 
ps.exec_pwrshell(pwr_script='test.ps1',script_path=r'C:\Path') # Mixed array/arraylist
(0, ['a', 'b', 'c', 'd', 'e', 1, 2, 3])
 
ps.exec_pwrshell(pwr_script='test.ps1',script_path=r'C:\Path') # Int
(0, 1)
 
ps.exec_pwrshell(pwr_script='test.ps1',script_path=r'C:\Path') # String
(0, 'Hello')
 
# NOTE: Tuple returned is (process_return_code, powershell_return_data)