├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── profile ├── pspar.py ├── pspython.py ├── pspython.runtimeconfig.json └── psrun.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/powershell:lts-7.2-ubuntu-20.04 2 | 3 | RUN apt update && apt install -y python3.8-full python3-pip wget vim 4 | RUN pip install pythonnet --pre -U 5 | RUN wget "https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh" && \ 6 | chmod +x dotnet-install.sh && \ 7 | ./dotnet-install.sh -version 6.0.100 8 | 9 | ENV DOTNET_ROOT=/root/.dotnet 10 | ENV PATH="$PATH:/root/.dotnet" 11 | 12 | # save the profile which says how to demo 13 | RUN mkdir -p /root/.config/powershell 14 | COPY profile /root/.config/powershell/Microsoft.PowerShell_profile.ps1 15 | # save the files needed for the demos 16 | COPY pspython.runtimeconfig.json /root 17 | COPY pspython.py /root 18 | COPY pspar.py /root 19 | COPY psrun.py /root 20 | RUN chmod +x /root/pspython.py && \ 21 | chmod +x /root/pspar.py && \ 22 | chmod +x /root/psrun.py 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 James Truher [MSFT] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PsPython 2 | Hosting PowerShell in Python 3 | 4 | To create the docker image, type the following: 5 | 6 | ```powershell 7 | PS> docker build --tag pspython:demo . 8 | ``` 9 | 10 | to run the demo, type: 11 | 12 | ```powershell 13 | PS> docker run --rm -it pspython:demo 14 | ``` 15 | 16 | and follow the demo instructions 17 | -------------------------------------------------------------------------------- /profile: -------------------------------------------------------------------------------- 1 | set-location /root 2 | [environment]::CurrentDirectory = "/root" 3 | 4 | @' 5 | 6 | To run demo 1, type: 7 | ./pspar.py '$a = 1;$b =' "$PSHOME/Modules/PowerShellGet/PSModule.psm1" 8 | 9 | To run demo 2, type: 10 | ./psrun.py 11 | 12 | '@ 13 | -------------------------------------------------------------------------------- /pspar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from pspython import * 4 | 5 | # positional parameters 6 | scriptDefinition = sys.argv[1] 7 | scriptFile = sys.argv[2] 8 | 9 | print(f"{bcolors.HEADER}Parse a script '{scriptDefinition}':{bcolors.RESET}") 10 | parseResult = ParseScript(scriptDefinition) 11 | print(f"{bcolors.HEADER}Tokens:{bcolors.RESET}") 12 | parseResult.PrintTokens() 13 | print(f"{bcolors.HEADER}Errors:{bcolors.RESET}") 14 | parseResult.PrintErrors() 15 | print(f"{bcolors.HEADER}Variables:{bcolors.RESET}") 16 | varAst = parseResult.GetAst("VariableExpressionAst") 17 | PrintResults(varAst) 18 | 19 | print(f"{bcolors.HEADER}Press to continue{bcolors.RESET}") 20 | input() 21 | print(f"{bcolors.HEADER}parse a file '{scriptFile}'{bcolors.RESET}") 22 | parseResult = ParseFile(scriptFile) 23 | commandAst = parseResult.GetAst("CommandAst") 24 | commands = set() 25 | for c in commandAst: 26 | commandName = c.GetCommandName() 27 | # sometimes CommandName is null, don't include those 28 | if commandName != None: 29 | commands.add(c.GetCommandName().lower()) 30 | PrintResults(sorted(commands)) 31 | print(f"{bcolors.HEADER}How many unique commands?{bcolors.RESET}") 32 | print(len(commands)) 33 | 34 | # exit nicely 35 | Environment.ExitCode = 0 36 | 37 | -------------------------------------------------------------------------------- /pspython.py: -------------------------------------------------------------------------------- 1 | import os 2 | # DOTNET_ROOT is set in the docker image 3 | # If you're not using docker, set the value correctly and uncomment the next line 4 | # os.environ["DOTNET_ROOT"] = r'/pathto/.dotnet' 5 | # set this to the location of your PowerShell installation 6 | # this is the location in the container 7 | psHome = r'/opt/microsoft/powershell/7-lts/' 8 | 9 | # load up the clr 10 | from clr_loader import get_coreclr 11 | from pythonnet import set_runtime 12 | 13 | # this is set in the container 14 | runtimeConfig = r'/root/pspython.runtimeconfig.json' 15 | rt = get_coreclr(runtimeConfig) 16 | set_runtime(rt) 17 | import clr 18 | import sys 19 | import System 20 | from System import Environment 21 | 22 | # we need to load MMI before we get started otherwise we get a 23 | # strong name validation error 24 | mmi = psHome + r'Microsoft.Management.Infrastructure.dll' 25 | clr.AddReference(mmi) 26 | from Microsoft.Management.Infrastructure import * 27 | 28 | # load up the worker assembly 29 | sma = psHome + r'System.Management.Automation.dll' 30 | clr.AddReference(sma) 31 | from System.Management.Automation import * 32 | from System.Management.Automation.Language import Parser 33 | 34 | ps = PowerShell.Create() 35 | def PsRunScript(script): 36 | ps.Commands.Clear() 37 | ps.Commands.AddScript(script) 38 | result = ps.Invoke() 39 | rlist = [] 40 | for r in result: 41 | rlist.append(r) 42 | return rlist 43 | 44 | class bcolors: 45 | HEADER = '\033[95m' 46 | OKBLUE = '\033[94m' 47 | OKCYAN = '\033[96m' 48 | OKGREEN = '\033[92m' 49 | WARNING = '\033[93m' 50 | FAIL = '\033[91m' 51 | RESET = '\033[0m' 52 | BOLD = '\033[1m' 53 | UNDERLINE = '\033[4m' 54 | 55 | 56 | class ParseResult: 57 | def __init__(self, scriptDefinition, tupleResult): 58 | self.ScriptDefinition = scriptDefinition 59 | self.Ast = tupleResult[0] 60 | self.Tokens = tupleResult[1] 61 | self.Errors = tupleResult[2] 62 | 63 | def PrintAst(self): 64 | print(self.ast.Extent.Text) 65 | 66 | def PrintErrors(self): 67 | for e in self.Errors: 68 | print(str(e)) 69 | 70 | def PrintTokens(self): 71 | for t in self.Tokens: 72 | print(str(t)) 73 | 74 | def GetAst(self, astname): 75 | Func = getattr(System, "Func`2") 76 | func = Func[System.Management.Automation.Language.Ast, bool](lambda a : type(a).__name__ == astname) 77 | asts = self.Ast.FindAll(func, True) 78 | return asts 79 | 80 | def ParseScript(scriptDefinition): 81 | token = None 82 | error = None 83 | ast = Parser.ParseInput(scriptDefinition, token, error) 84 | pr = ParseResult(scriptDefinition, ast) 85 | return pr 86 | 87 | def ParseFile(filePath): 88 | token = None 89 | error = None 90 | ast = Parser.ParseFile(filePath, token, error) 91 | pr = ParseResult(filePath, ast) 92 | return pr 93 | 94 | def PrintResults(result): 95 | for r in result: 96 | print(r) 97 | -------------------------------------------------------------------------------- /pspython.runtimeconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimeOptions": { 3 | "tfm": "net6.0", 4 | "framework": { 5 | "name": "Microsoft.NETCore.App", 6 | "version": "6.0.0" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /psrun.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from pspython import * 4 | 5 | # run demo 1 6 | # return objects to python for printing 7 | scriptDefinition = 'Get-ChildItem' 8 | print(f"{bcolors.HEADER}run the script: {scriptDefinition}{bcolors.RESET}") 9 | result = PsRunScript(scriptDefinition) 10 | PrintResults(result) 11 | 12 | # run demo 1 13 | # return objects to python, filter them (in python), then format them with PowerShell 14 | print(f"\n{bcolors.HEADER}input provided by python with formatting{bcolors.RESET}") 15 | # part 1 - gather object 16 | scriptDefinition = 'Get-ChildItem' 17 | result = list(filter(lambda r: r.BaseObject.Name.startswith('ps'), PsRunScript(scriptDefinition))) 18 | # part 2 - use those results as input for PowerShell 19 | ps.Commands.Clear() 20 | ps.Commands.AddCommand("Out-String").AddParameter("Stream",True).AddParameter("InputObject", result) 21 | strResult = ps.Invoke() 22 | # print results 23 | PrintResults(strResult) 24 | 25 | # exit nicely 26 | Environment.ExitCode = 0 27 | --------------------------------------------------------------------------------