Hosting PowerShell in a Python script

This post was originally published on this site

Yes Virginia, languages other than PowerShell do exist.

I was working with a partner group here at Microsoft and they explained that they wanted to parse PowerShell scripts from Python.
Their natural approach was to invoke the PowerShell executable and construct a command-line that did what they needed.
I thought there might be a better way as creating a new PowerShell process each time is expensive, so I started doing a bit of research to see something could be done.
I’ve been aware of IronPython (Python that tightly integrates .NET) for a long time, and
we met with Jim Hugunin shortly after he arrived at Microsoft and PowerShell was just getting underway,
but the group is using cPython so I went hunting for Python modules that host .NET and found the pythonnet module.

The pythonnet package gives Python developers extremely easy access to the dotnet runtime from Python.
I thought this package might be the key for accessing PowerShell,
after some investigation I found that it has exactly what I needed to host PowerShell in a Python script.

The guts

I needed to figure out a way to load the PowerShell engine.
First, there are a couple of requirements to make this all work.
Dotnet has to be available, as does PowerShell and pythonnet provides a way to specify where to look for dotnet.
Setting the environment variable DOTNET_ROOT to the install location,
enables pythonnet a way find the assemblies and other support files to host .NET.

import os
os.environ["DOTNET_ROOT"] = "/root/.dotnet"

Now that we know where dotnet is, we need to load up the CLR and set up the runtime configuration.
The runtime configuration describes various aspects of how we’ll run.
We can create a very simple pspython.runtimeconfig.json

{
  "runtimeOptions": {
    "tfm": "net6.0",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "6.0.0"
    }
  }
}

The combination of the DOTNET_ROOT and the runtime configuration enables
loading the CLR with the get_coreclr and set_runtime functions.

# load up the clr
from clr_loader import get_coreclr
from pythonnet import set_runtime
rt = get_coreclr("/root/pspython.runtimeconfig.json")
set_runtime(rt)

Now that we have the CLR loaded, we need to load the PowerShell engine.
This was a little non-obvious.
Initially, I just attempted to load System.Management.Automation.dll but that failed
due to a strong name validation error.
However, If I loaded Microsoft.Management.Infrastructure.dll first, I can avoid that error.
I’m not yet sure about why I need to load this assembly first, that’s still something
I need to determine.

import clr
import sys
import System
from System import Environment
from System import Reflection

psHome = r'/opt/microsoft/powershell/7/'

mmi = psHome + r'Microsoft.Management.Infrastructure.dll'
clr.AddReference(mmi)
from Microsoft.Management.Infrastructure import *

full_filename = psHome + r'System.Management.Automation.dll'
clr.AddReference(full_filename)
from System.Management.Automation import *
from System.Management.Automation.Language import Parser

Eventually I would like to make the locations of dotnet and PSHOME configurable,
but for the moment, I have what I need.

Now that the PowerShell engine is available to me,
I created a couple of helper functions to make handling the results easier from Python.
I also created a PowerShell object (PowerShell.Create()) that I will use in some of my functions.

ps = PowerShell.Create()
def PsRunScript(script):
    ps.Commands.Clear()
    ps.Commands.AddScript(script)
    result = ps.Invoke()
    rlist = []
    for r in result:
        rlist.append(r)
    return rlist

class ParseResult:
    def __init__(self, scriptDefinition, tupleResult):
        self.ScriptDefinition = scriptDefinition
        self.Ast = tupleResult[0]
        self.Tokens = tupleResult[1]
        self.Errors = tupleResult[2]

    def PrintAst(self):
        print(self.ast.Extent.Text)

    def PrintErrors(self):
        for e in self.Errors:
            print(str(e))

    def PrintTokens(self):
        for t in self.Tokens:
            print(str(t))

    def FindAst(self, astname):
        Func = getattr(System, "Func`2")
        func = Func[System.Management.Automation.Language.Ast, bool](lambda a : type(a).__name__ == astname)
        asts = self.Ast.FindAll(func, True)
        return asts

def ParseScript(scriptDefinition):
    token = None
    error = None
    # this returns a tuple of ast, tokens, and errors rather than the c# out parameter
    ast = Parser.ParseInput(scriptDefinition, token, error)
    # ParseResult will bundle the 3 parts into something more easily consumed.
    pr = ParseResult(scriptDefinition, ast)
    return pr

def ParseFile(filePath):
    token = None
    error = None
    # this returns a tuple of ast, tokens, and errors rather than the c# out parameter
    ast = Parser.ParseFile(filePath, token, error)
    # ParseResult will bundle the 3 parts into something more easily consumed.
    pr = ParseResult(filePath, ast)
    return pr

def PrintResults(result):
    for r in result:
        print(r)

I really wanted to mimic the PowerShell AST methods with some more friendly Python functions.
To create the FindAst() function, I needed to combine the delegate in c# with the lambda feature in Python.
Normally, in PowerShell, this would look like:

$ast.FindAll({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $true)

But I thought from a Python script, it would easier to use the name of the type.
You still need to know the name of the type,
but bing is great for that sort of thing.
As I said, I don’t really know the Python language,
so I expect there are better ways to handle the Collection[PSObject] that Invoke() returns.
I found that I had to iterate over the result no matter what, so I built it into the convenience function.
Anyone with suggestions is more than welcome to improve this.

The glory

Now that we have the base module together, we can write some pretty simple Python to
execute our PowerShell scripts.
Invoking a PowerShell script is now as easy as:

#!/usr/bin/python3

from pspython import *

scriptDefinition = 'Get-ChildItem'
print(f"Run the script: '{scriptDefinition}")
result = PsRunScript(scriptDefinition)
PrintResults(result)
/root/__pycache__
/root/dotnet-install.sh
/root/get-pip.py
/root/grr.py
/root/hosted.runtimeconfig.json
/root/pspar.py
/root/pspython.py
/root/psrun.py

You’ll notice that the output is not formatted by PowerShell.
This is because Python is just taking the .NET objects and (essentially) calling ToString() on them.

It’s also possible to retrieve objects and then manage formatting via PowerShell.
This example retrieves objects via Get-ChildItem,
selects those files that start with “ps” in Python,
and then creates a string result in table format.

scriptDefinition = 'Get-ChildItem'
result = list(filter(lambda r: r.BaseObject.Name.startswith('ps'), PsRunScript(scriptDefinition)))
ps.Commands.Clear()
ps.Commands.AddCommand("Out-String").AddParameter("Stream", True).AddParameter("InputObject", result)
strResult = ps.Invoke()
# print results
PrintResults(strResult)
    Directory: /root

UnixMode   User             Group                 LastWriteTime           Size Name
--------   ----             -----                 -------------           ---- ----
-rwxr-xr-x root             dialout             6/17/2022 01:30           1117 pspar.py
-rwxr-xr-x root             dialout             6/16/2022 18:55           2474 pspython.py
-rwxr-xr-x root             dialout             6/16/2022 21:43            684 psrun.py

But that’s not all

We can also call static methods on PowerShell types.
Those of you that noticed in my module there are a couple of language related functions.
The ParseScript and ParseFile functions allow us to call the PowerShell language parser
enabling some very interesting scenarios.

Imagine I wanted to determine what commands a script is calling.
The PowerShell AST makes that a breeze, but first we have to use the parser.
In PowerShell, that would be done like this:

$tokens = $errors = $null
$AST = [System.Management.Automation.Language.Parser]::ParseFile("myscript.ps1", [ref]$tokens, [ref]$errors)

The resulting AST is stored in $AST, the tokens in $tokens, and the errors in $errors.
With this Python module, I encapsulate that into the Python function ParseFile,
which returns an object containing all three of those results in a single element.
I also created a couple of helper functions to print the tokens and errors more easily.
Additionally, I created a function that allows me to look for any type of AST (or sub AST)
in any arbitrary AST.

parseResult = ParseFile(scriptFile)
commandAst = parseResult.FindAst("CommandAst")
commands = set()
for c in commandAst:
    commandName = c.GetCommandName()
    # sometimes CommandName is null, don't include those
    if commandName != None:
       commands.add(c.GetCommandName().lower())
PrintResults(sorted(commands))

Note that there is a check for commandName not being null.
This is because when & $commandName is used, the command name cannot be
determined via static analysis since the command name is determined at run-time.

…a few, uh, provisos, uh, a couple of quid pro quo

First, you have to have dotnet installed (via the install-dotnet),
as well as a full installation of PowerShell.
pythonnet doesn’t run on all versions of Python,
I’ve tested it only on Python 3.8 and Python 3.9 on Ubuntu20.04.
As of the time I wrote this, I couldn’t get it to run on Python 3.10.
There’s more info on pythonnet at the pythonnet web-site.
Also, this is a hosted instance of PowerShell.
Some things, like progress, and verbose, and errors may act a bit differently than you
would see from pwsh.exe.
Over time, I will probably add additional helper functions to retrieve more runtime information
from the engine instance.
If you would like to pitch in, I’m happy to take Pull Requests or to simply understand your use cases integrating PowerShell and Python.

Take it out for a spin

I’ve wrapped all of this up and added a Dockerfile (running on Ubuntu 20.04) on
github.
To create the docker image, just run
Docker build --tag pspython:demo .
from the root of the repository.

The post Hosting PowerShell in a Python script appeared first on PowerShell Team.

vMotion issue when VM on a VVOL Datastore

Hi,

 

i installed im my lab with a vSphere 7.0.1 enviroment the NetApp VSC 9.7.1 wich brings the VASA provider for VVOLs to thest them on my NetApp Storage connected via NFS.

 

Installation works without issues and even the migration from the NFS 4.1 datastores to the VVOL datastore.

 

Then i noticed a problem with the VCSA: VCSA on a VVOL? Is this supported?

 

After a little bit more testing i noticed that this is a general vMotion problem when the VM is on a VVOL datastore. When the same VM is on a FS 4.1 datastore i have no issue.

 

I opened a case first at NetApp, but they didin’t found something in the logs and told me to open a case at vmware too. There i opened one week ago a case and provided all logs. But they took a very long time to decide which department should work on the case. Yesterday i talked first time with the support and today we finished the tests to be clear, yes the problem is only during a normal vMotion and only when the VM is on a VVOL. So he will forward the case to the storage guys.

 

While i’m waiting for the support, i think i write down here my issue perhaps someone esle had a similar problem…

 

Problem:

 

vMotion from one “host” to a other “host” stuck at 85% and then VM freezes for about 30 sec, then the vMotion continues and finishes. Then the VM is running again.

 

Here i have some log parts:

 

Log of the VM from source-host:

2020-10-14T15:52:14.200Z| vmx| W003: VMX has left the building: 0.

 

VMKernel from source-host:

2020-10-14T15:52:14.251Z cpu4:2105329)VVol: VVolRemoveDev:7163: Unlinking (VVOL_OBJTYPE_VMDK) VVol device rfc4122.80207299-548e-459c-bc0c-4d45318cfae2

2020-10-14T15:52:14.332Z cpu18:2099869)VVol: VVolRemoveDev:7163: Unlinking (VVOL_OBJTYPE_CONFIG) VVol device rfc4122.1edaed3d-4db9-44d6-a945-79567334ffa0

 

The VM has left the host at 17:52:14, so it must be started in the same sec on the destination…

 

Log of the VM from the destination-host:

2020-10-14T15:52:14.190Z| vcpu-0| I005: Transitioned vmx/execState/val to poweredOn

2020-10-14T15:52:14.191Z| vcpu-0| I005: MigrateSetState: Transitioning from state 12 to 0.

2020-10-14T15:52:54.205Z| vmx| I005: DiskUpgradeMultiwriter: Upgraded open disk ‘scsi0:0’ from multiwriter.

 

Here is a large gap between the sec 14 and 54 in the log, there is no message.

 

VMKernel from the destination-host:

2020-10-14T15:52:12.956Z cpu3:2103898)VVol: VVolMakeDev:6740: Creating a device for rfc4122.1edaed3d-4db9-44d6-a945-79567334ffa0 (Type VVOL_OBJTYPE_CONFIG)

2020-10-14T15:52:13.264Z cpu16:2103911)VVol: VVolMakeDev:6740: Creating a device for rfc4122.80207299-548e-459c-bc0c-4d45318cfae2 (Type VVOL_OBJTYPE_UNKNOWN)

2020-10-14T15:52:14.190Z cpu25:2103920)Hbr: 3731: Migration end received (worldID=2103906) (migrateType=1) (event=1) (isSource=0) (sharedConfig=1)

2020-10-14T15:52:14.191Z cpu8:2103915)VMotion: 3230: 8288837917254555216 D: VMotion bandwidth in last 1s: 27 MB/s,

2020-10-14T15:52:14.194Z cpu3:2103923)Swap: vm 2103906: 5135: Finish swapping in migration swap file. (faulted 0 pages). Success.

2020-10-14T15:52:44.200Z cpu25:2103905)NFSLock: 3302: lock .lck-1c7bdce900000000 expired: counter prev 584 3fc5805f-1e9c2009-3763-ac1f6bc58788 : curr 584 3fc5805f-1e9c2009-3763-ac1f6bc58788 (loop count 3)

 

This message i’m wondering about…

 

Hostd from the destination-host:

2020-10-14T15:52:13.138Z verbose hostd[2099792] [Originator@6876 sub=Vigor.Vmsvc.vm:/vmfs/volumes/vvol:fb1e3913ec4448e4-bf4e00000098990c/rfc4122.1edaed3d-4db9-44d6-a945-79567334ffa0/srv15 – Web-Server.vmx] VMotion destination started; powering on

2020-10-14T15:52:13.213Z info hostd[2100209] [Originator@6876 sub=Vmsvc.vm:/vmfs/volumes/vvol:fb1e3913ec4448e4-bf4e00000098990c/rfc4122.1edaed3d-4db9-44d6-a945-79567334ffa0/srv15 – Web-Server.vmx] VigorMigrateNotifyCb:: hostlog state changed from emigrating to none

2020-10-14T15:52:54.219Z verbose hostd[2100094] [Originator@6876 sub=Vmsvc.vm:/vmfs/volumes/vvol:fb1e3913ec4448e4-bf4e00000098990c/rfc4122.1edaed3d-4db9-44d6-a945-79567334ffa0/srv15 – Web-Server.vmx] VMotionStatusCb [8288837917254555216]: Succeeded

2020-10-14T15:52:54.219Z verbose hostd[2100094] [Originator@6876 sub=Vmsvc.vm:/vmfs/volumes/vvol:fb1e3913ec4448e4-bf4e00000098990c/rfc4122.1edaed3d-4db9-44d6-a945-79567334ffa0/srv15 – Web-Server.vmx] VMotionStatusCb: Firing ResolveCb

2020-10-14T15:52:54.219Z info hostd[2100094] [Originator@6876 sub=Vcsvc.VMotionDst.8288837917254555216] ResolveCb: VMX reports needsUnregister = false for migrateType MIGRATE_TYPE_VMOTION

2020-10-14T15:52:54.219Z info hostd[2100094] [Originator@6876 sub=Vcsvc.VMotionDst.8288837917254555216] ResolveCb: Succeeded

2020-10-14T15:52:54.220Z info hostd[2100094] [Originator@6876 sub=Vmsvc.vm:/vmfs/volumes/vvol:fb1e3913ec4448e4-bf4e00000098990c/rfc4122.1edaed3d-4db9-44d6-a945-79567334ffa0/srv15 – Web-Server.vmx] Disk access enabled.

2020-10-14T15:52:54.221Z info hostd[2100094] [Originator@6876 sub=Vmsvc.vm:/vmfs/volumes/vvol:fb1e3913ec4448e4-bf4e00000098990c/rfc4122.1edaed3d-4db9-44d6-a945-79567334ffa0/srv15 – Web-Server.vmx] State Transition (VM_STATE_IMMIGRATING -> VM_STATE_ON)

2020-10-14T15:52:54.225Z info hostd[2100094] [Originator@6876 sub=Vmsvc.vm:/vmfs/volumes/vvol:fb1e3913ec4448e4-bf4e00000098990c/rfc4122.1edaed3d-4db9-44d6-a945-79567334ffa0/srv15 – Web-Server.vmx] Send config update invoked

 

Here the same gap. Here i’m wondering abut the message „Disk access enabled“ in the sec 54, why so late?

 

The main question, what happens between the sec 14 und 54 and how to fix that?

 

Kind regards

Stefan

SSDP blocker

Hi
I have a very wired problem with my VM.
I’m using WMware Workstation 15 Pro on Windows 10 and created an VM which is running a Debian Linux (Raspbian Desktop) which has setup a network bridge.
On the VM I’m scanning the network for SSDP requests but I’m not able to receive all SSDP packages for some reason.
I then startet to play with the network configuration of VMWare. If my host is connected by WiFi and I set the bridge setting to automatic or select the WiFi interface, I don’t receive all SSDP packages within the VM. But on the host itself I receive all SSDP packages from the network.
If I then connect the host to the same network by ethernet cable and then set the bridge to the ethernet interface everything works fine. I receive all SSDP packages on the host as well as in the VM.
To me this makes no sense that it doesn’t also work with the WiFi interface.
I converted the VM into a ovf to run it with virtual box and there everything worked with the WiFi interface.
Is this maybe a bug of VMWare?

 

Thanks for your help.

 

Kevin

Not sure it's possible to update via alternate link in vCenter/Esxi

Id like to update vCenter and ESXI without having a gateway on my Management Network as denoted in vCenter. Instead I would like to update using the second nic connected to vCenter which does have a gateway and allows only vCenter updates though.

 

My lab is air gapped. I dont want to expose management services to the public internet. I will only use this other NIC1 as the update NIC, using vCenter as a ?proxy? tot he ESXI hosts.

 

If I need to I can put a VMK on the ESXI hosts to reach the public as well, but would rather not put Management services on that VMK. This would allow my system to be connected from the Public Network, defeating the idea of Air Gapped – I will only use this second connection while updating vCenter and ESXi hosts.

 

NIC0 – Management Network – No Gateway

NIC1 – DHCP with access to Internet (no management services assigned)

 

Is this possible? Am I missing something?

 

Maybe I should just make a soft proxy vm or something.

 

 

Thanks,

Eric

NSXY-T limited export version issue

Hi Community, we are facing the following problem: We downloaded a NSX-T Evaluation Version from VMWare und tested our planned deployment.

 

After successful tests we decided to use this already working setup as productive deployment.

 

Now we stumbled across the issue, that in the eval-version there is no IPSec and L2TP available due to export restrictions, it is a “limited export” version.

 

Now here is my question: can we backup the manager node configurations, install the regular NSX-T version from VMWare and restore the backup to the manager nodes without issues in regards to the limited export limitations? Do we need to redeploy edge nodes or reinstall  NSXT agents on the hosts? Maybe somebody has done this before? 

 

An answer would be highly appreciated!

Thanks to all of you!

 

Micha

How to avoid VPN on host and only connect to LAN on host?

So I’ve got this work computer… yes it has too many group policies preventing me from what I want to do so I installed a VM using Player trying to set up my own stuff in the VM. I’m struggling to set up the VM’s network so it bypasses work VPN on the host and only acts as another computer on my LAN. Is this possible?

 

The VPN is Citrix Gateway, the network connection for the VPN is on Citrix Virtual Adapter network adapter. Windows 10 Enterprise.

 

In addition, the PC has cellular capability. But I don’t think it’s possible to bridge the VM to the cellular adapter? It’s a DW5820e Intel 7360 LTE-A cellular adapter.

 

I could set up a proxy server on my LAN and just use the proxy server. Citrix Gateway is letting me have access to my LAN. But I want to explore if it’s possible to have the VM completely separate from the VPN connection and only use my LAN connection, wired or wireless.

DEM Not Applying Default Apps

Hello there,

 

We have a new win 10 1909 environment we’re trying to roll out and we’re having extreme issues with DEM not applying/saving default apps and FTAs correctly and we cannot figure out why.

 

I have the Default Apps and FTAs flex engine enabled with a predefined forced setting to set Internet Explorer as default browser and to set adobe reader to open PDFs.

 

It seems to save and apply the adobe reader FTA, and it actually seems to map .http and .https files to Internet Explorer, but it only makes IE the default browser about 40-50% of the time, so when we click on a link inside of outlook it opens up in edge.

 

I worked with support and they had me drop the .xml file in the file type association page on DEM as well and like I said that part seems to be applying correctly just not for the default application part.

 

I’ll attach several pictures and a log of my latest login to see if that will help any.

 

I do appreciate it!

 

Edit: I apologize, I forgot to list that we are using DEM version 9.11 as well.

YARA’s XOR Modifier, (Mon, Oct 14th)

YARA searches for strings inside files. Strings to search for are defined with YARA rules.

With the release of YARA 3.8.0, support for searching for XOR encoded strings was introduced. By adding the modifier xor to the definition of a string, YARA 3.8.0 would search for strings that were XOR encoded, with a single-byte key, ranging from 1 to 255.

Here is an example of a string with xor modifier.

    rule xor_test {
        strings:
            $a = “https://isc.sans.edu” xor
        condition:
            $a
    }

This YARA version’s xor modifier would not match unencoded strings.

Apparently, that was not the purpose, and this was fixed with version 3.10.0.

The same rule would now also match unencoded strings.

With the latest version of YARA, 3.11.0, a YARA rule developer has now control over which XOR key range is used by modifier xor.

This is done by specifing an optional minimum-key – maximum-key range after the xor modifier, like this: xor(min-max).

The following rule has an xor modifier with key range 0x01-0xFF (minimum/maximum keys can be specified with decimal or hexadecimal values).

    rule xor_test {
        strings:
            $a = “https://isc.sans.edu” xor(0x01-0xFF)
        condition:
            $a
    }

This rule will not match unencoded strings.

 

Didier Stevens
Senior handler
Microsoft MVP
blog.DidierStevens.com DidierStevensLabs.com

(c) SANS Internet Storm Center. https://isc.sans.edu Creative Commons Attribution-Noncommercial 3.0 United States License.